Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Resolve addresses by three word addresses #14

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;

import static net.contargo.iris.address.w3w.ThreeWordMatcher.isThreeWordAddress;

import static org.slf4j.LoggerFactory.getLogger;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
Expand Down Expand Up @@ -144,6 +147,15 @@ public ListOfAddressListsResponse addressesByAddressDetails(@RequestParam(requir
}
}

if (isThreeWordAddress(street)) {
Optional<AddressDto> addressDto = addressDtoService.getAddressesByThreeWords(street.trim());

if (addressDto.isPresent()) {
addressListDtos = singletonList(new AddressListDto("Result for three words " + street,
singletonList(addressDto.get())));
}
}

Map<String, String> addressDetails = NominatimUtil.parameterMap(street, postalCode, city, country, name);

if (addressListDtos.isEmpty()) {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/net/contargo/iris/address/dto/AddressDtoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.util.List;
import java.util.Map;
import java.util.Optional;


/**
Expand Down Expand Up @@ -116,4 +117,14 @@ public interface AddressDtoService {
* @return a list of matching addresses
*/
List<AddressDto> getAddressesByQuery(String query);


/**
* Returns an optional {@link AddressDto} matching the given three word address.
*
* @param threeWords a three word address
*
* @return an optional address
*/
Optional<AddressDto> getAddressesByThreeWords(String threeWords);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
Expand Down Expand Up @@ -110,4 +111,13 @@ public List<AddressDto> getAddressesByQuery(String query) {

return addressServiceWrapper.getAddressesByQuery(query).stream().map(AddressDto::new).collect(toList());
}


@Override
public Optional<AddressDto> getAddressesByThreeWords(String threeWords) {

Optional<Address> optionalAddress = addressServiceWrapper.getAddressByThreeWords(threeWords);

return optionalAddress.map(AddressDto::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import net.contargo.iris.address.staticsearch.StaticAddress;
import net.contargo.iris.address.staticsearch.service.StaticAddressNotFoundException;
import net.contargo.iris.address.staticsearch.service.StaticAddressService;
import net.contargo.iris.address.w3w.ThreeWordClient;
import net.contargo.iris.address.w3w.ThreeWordClientException;
import net.contargo.iris.normalizer.NormalizerService;

import org.apache.commons.lang.StringUtils;
Expand All @@ -19,6 +21,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

import static net.contargo.iris.address.nominatim.service.AddressDetailKey.CITY;
Expand Down Expand Up @@ -46,14 +49,16 @@ public class AddressServiceWrapper {
private final StaticAddressService staticAddressService;
private final AddressCache addressCache;
private final NormalizerService normalizerService;
private final ThreeWordClient threeWordClient;

public AddressServiceWrapper(AddressService addressService, StaticAddressService staticAddressService,
AddressCache cache, NormalizerService normalizerService) {
AddressCache cache, NormalizerService normalizerService, ThreeWordClient threeWordClient) {

this.addressService = addressService;
this.staticAddressService = staticAddressService;
this.addressCache = cache;
this.normalizerService = normalizerService;
this.threeWordClient = threeWordClient;
}

/**
Expand Down Expand Up @@ -225,4 +230,19 @@ public List<Address> getAddressesByQuery(String query) {

return addresses;
}


@SuppressWarnings("squid:S1166")
public Optional<Address> getAddressByThreeWords(String threeWords) {

try {
GeoLocation resolvedLocation = threeWordClient.resolve(threeWords);

return Optional.of(getAddressForGeoLocation(resolvedLocation));
} catch (ThreeWordClientException e) {
LOG.info("Cannot resolve three word address {}: {}", threeWords, e.getMessage());

return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package net.contargo.iris.address.w3w;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import net.contargo.iris.GeoLocation;

import java.math.BigDecimal;


/**
* @author Sandra Thieme - thieme@synyx.de
*/
class ForwardW3wResponse {

private final W3wResponseGeometry geometry;
private final W3wResponseStatus status;

@JsonCreator
ForwardW3wResponse(@JsonProperty("geometry") W3wResponseGeometry geometry,
@JsonProperty("status") W3wResponseStatus status) {

this.geometry = geometry;
this.status = status;
}

GeoLocation toGeolocation() {

return new GeoLocation(geometry.lat, geometry.lon);
}


boolean error() {

return status.code != null;
}


Integer errorCode() {

return status.code;
}


String errorMessage() {

return status.message;
}

private static class W3wResponseGeometry {

private final BigDecimal lat;
private final BigDecimal lon;

@JsonCreator
private W3wResponseGeometry(@JsonProperty("lat") BigDecimal lat,
@JsonProperty("lng") BigDecimal lon) {

this.lat = lat;
this.lon = lon;
}
}

private static class W3wResponseStatus {

private final Integer code;
private final String message;

@JsonCreator
private W3wResponseStatus(@JsonProperty("code") Integer code,
@JsonProperty("message") String message) {

this.code = code;
this.message = message;
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/net/contargo/iris/address/w3w/ThreeWordClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package net.contargo.iris.address.w3w;

import net.contargo.iris.GeoLocation;

import org.springframework.http.ResponseEntity;

import org.springframework.web.client.RestTemplate;


/**
* @author Sandra Thieme - thieme@synyx.de
*/
public class ThreeWordClient {

private static final String FORWARD_URL =
"https://api.what3words.com/v2/forward?addr={w3wAddress}&key={apiKey}&lang=de";

private final RestTemplate restTemplate;
private final String apiKey;

public ThreeWordClient(RestTemplate restTemplate, String apiKey) {

this.restTemplate = restTemplate;
this.apiKey = apiKey;
}

public GeoLocation resolve(String threeWords) {

ResponseEntity<ForwardW3wResponse> response = restTemplate.getForEntity(FORWARD_URL, ForwardW3wResponse.class,
threeWords, apiKey);

checkErrorStatus(response.getBody(), threeWords);

return response.getBody().toGeolocation();
}


private static void checkErrorStatus(ForwardW3wResponse response, String threeWords) {

if (response.error()) {
Integer code = response.errorCode();
String message = response.errorMessage();

throw new ThreeWordClientException(code, message, threeWords);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.contargo.iris.address.w3w;

/**
* @author Sandra Thieme - thieme@synyx.de
*/
public class ThreeWordClientException extends RuntimeException {

ThreeWordClientException(Integer code, String message, String threeWords) {

super("API of w3w returned error code " + code + " with message '" + message
+ "' for three word address '" + threeWords + "'");
}
}
17 changes: 17 additions & 0 deletions src/main/java/net/contargo/iris/address/w3w/ThreeWordMatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package net.contargo.iris.address.w3w;

import java.util.regex.Pattern;


/**
* @author Sandra Thieme - thieme@synyx.de
*/
public class ThreeWordMatcher {

private static final Pattern THREE_WORD_PATTERN = Pattern.compile("^\\p{L}+\\.\\p{L}+\\.\\p{L}+$");

public static boolean isThreeWordAddress(String input) {

return input != null && THREE_WORD_PATTERN.matcher(input.trim()).matches();
}
}
8 changes: 8 additions & 0 deletions src/main/resources/application-context.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,16 @@
</bean>
</constructor-arg>
<constructor-arg name="normalizerService" ref="normalizerService"/>
<constructor-arg name="threeWordClient" ref="threeWordClient"/>
</bean>

<bean id="threeWordClient" class="net.contargo.iris.address.w3w.ThreeWordClient">
<constructor-arg>
<bean class="org.springframework.web.client.RestTemplate"/>
</constructor-arg>
<constructor-arg name="apiKey" value="${w3w.apikey}"/>
</bean>

<bean id="addressListFilter" class="net.contargo.iris.address.service.AddressListFilterImpl"/>

<bean id="addressDtoService" class="net.contargo.iris.address.dto.AddressDtoServiceImpl">
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@ cookies.link=https://example.com
routing.threads=10

routeDataRevision.mandatoryForSwissAddress=true


w3w.apikey=
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static org.hamcrest.MatcherAssert.assertThat;

Expand Down Expand Up @@ -169,6 +170,32 @@ public void addressesByAddressDetailsWithHashKey() throws Exception {
}


@Test
public void addressesByAddressDetailsWithThreeWords() throws Exception {

AddressDto addressDto = new AddressDto(new Address("Resolved address"));
when(addressDtoServiceMock.getAddressesByThreeWords("one.two.three")).thenReturn(Optional.of(addressDto));

ResultActions resultActions = mockMvc.perform(get("/geocodes/?street=one.two.three").accept(APPLICATION_JSON));
resultActions.andExpect(status().isOk());
resultActions.andExpect(content().contentType("application/json"));
resultActions.andExpect(jsonPath("$.geoCodeResponse.addresses[0].addresses[0].displayName").value(
"Resolved address"));
}


@Test
public void addressesByAddressDetailsWithFailure() throws Exception {

when(addressDtoServiceMock.getAddressesByThreeWords("one.two.three")).thenReturn(Optional.empty());

ResultActions resultActions = mockMvc.perform(get("/geocodes/?street=one.two.three").accept(APPLICATION_JSON));
resultActions.andExpect(status().isOk());
resultActions.andExpect(content().contentType("application/json"));
resultActions.andExpect(jsonPath("$.geoCodeResponse.addresses").isEmpty());
}


@Test
public void addressesByAddressDetailsWithHashKeyAndException() throws Exception {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static net.contargo.iris.address.nominatim.service.AddressDetailKey.CITY;
import static net.contargo.iris.address.nominatim.service.AddressDetailKey.COUNTRY;
Expand Down Expand Up @@ -211,4 +212,16 @@ public void getAddressesByQuery() {
assertThat(addresses, hasSize(1));
assertThat(addresses.get(0).getDisplayName(), is("Gartenstr. 67, Karlsruhe (Südweststadt)"));
}


@Test
public void getAddressesByThreeWords() {

when(addressServiceWrapperMock.getAddressByThreeWords("riches.lofts.guessing")).thenReturn(Optional.of(
new Address("German Chancellery")));

Optional<AddressDto> dto = sut.getAddressesByThreeWords("riches.lofts.guessing");

assertThat(dto.get().getDisplayName(), is("German Chancellery"));
}
}
Loading