Approach sounds reasonable. Does geocoder return speed limit in the API response?
Not the default response, but you can call /details with place_id and that will have an extras->maxspeed object if applicable.
I still need to work out the unit to store in, nominatim will return km/h as a number or 'xx mph' if in mph. This will probably be related to the attributes by the look of it.
The approach I am taking is to extend Geocoder with:
String getSpeedLimit(String way, ReverseGeocoderCallback callback);
And extend JsonGeocoder as:
public JsonGeocoder(String url, final int cacheSize, AddressFormat addressFormat, String speedurl) {}
with a fallback for providers that do not support speed limits:
public JsonGeocoder(String url, final int cacheSize, AddressFormat addressFormat) {}
Then the providers are extended with:
private static String formatSpeedUrl(String url, String key, String language) {}
and in parseAddress call:
address.setWay(result.getString("place_id"));
Then after a succesful call
handleSpeedResponse(String way, JsonObject json, ReverseGeocoderCallback callback){}
will format the speed and return it.
Then I need to figure out how to link that to the device attribute... getting there.
Changes so far:
diff --git a/src/org/traccar/geocoder/Address.java b/src/org/traccar/geocoder/Address.java
index fe39da8e..ea2570c1 100644
--- a/src/org/traccar/geocoder/Address.java
+++ b/src/org/traccar/geocoder/Address.java
@@ -106,5 +106,24 @@ public class Address {
public void setFormattedAddress(String formattedAddress) {
this.formattedAddress = formattedAddress;
}
+
+ private String speedLimit;
+ public String getSpeedLimit() {
+ return speedLimit;
+ }
+
+ public void setSpeedLimit(String speedLimit) {
+ this.speedLimit = speedLimit;
+ }
+
+ private String way;
+
+ public String getWay() {
+ return way;
+ }
+
+ public void setWay(String way) {
+ this.way = way;
+ }
}
diff --git a/src/org/traccar/geocoder/AddressFormat.java b/src/org/traccar/geocoder/AddressFormat.java
index ad19432b..08c18193 100644
--- a/src/org/traccar/geocoder/AddressFormat.java
+++ b/src/org/traccar/geocoder/AddressFormat.java
@@ -68,6 +68,7 @@ public class AddressFormat extends Format {
result = replace(result, "%r", address.getStreet());
result = replace(result, "%h", address.getHouse());
result = replace(result, "%f", address.getFormattedAddress());
+ result = replace(result, "%z", address.getSpeedLimit());
result = result.replaceAll("^[, ]*", "");
diff --git a/src/org/traccar/geocoder/Geocoder.java b/src/org/traccar/geocoder/Geocoder.java
index 587a2752..460cf6b1 100644
--- a/src/org/traccar/geocoder/Geocoder.java
+++ b/src/org/traccar/geocoder/Geocoder.java
@@ -26,5 +26,6 @@ public interface Geocoder {
}
String getAddress(double latitude, double longitude, ReverseGeocoderCallback callback);
+ String getSpeedLimit(String way, ReverseGeocoderCallback callback);
}
diff --git a/src/org/traccar/geocoder/JsonGeocoder.java b/src/org/traccar/geocoder/JsonGeocoder.java
index ed59a1d8..bcd1581f 100644
--- a/src/org/traccar/geocoder/JsonGeocoder.java
+++ b/src/org/traccar/geocoder/JsonGeocoder.java
@@ -33,9 +33,31 @@ public abstract class JsonGeocoder implements Geocoder {
private static final Logger LOGGER = LoggerFactory.getLogger(JsonGeocoder.class);
private final String url;
+ private String speedurl = null;
private final AddressFormat addressFormat;
private Map<Map.Entry<Double, Double>, String> cache;
+ private Map<String, String> speedcache;
+
+ public JsonGeocoder(String url, final int cacheSize, AddressFormat addressFormat, String speedurl) {
+ this.url = url;
+ this.speedurl = speedurl;
+ this.addressFormat = addressFormat;
+ if (cacheSize > 0) {
+ this.cache = Collections.synchronizedMap(new LinkedHashMap<Map.Entry<Double, Double>, String>() {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > cacheSize;
+ }
+ });
+ this.speedcache = Collections.synchronizedMap(new LinkedHashMap<String, String>() {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > cacheSize;
+ }
+ });
+ }
+ }
public JsonGeocoder(String url, final int cacheSize, AddressFormat addressFormat) {
this.url = url;
@@ -73,6 +95,23 @@ public abstract class JsonGeocoder implements Geocoder {
}
return null;
}
+
+ private String handleSpeedResponse(
+ String way, JsonObject json, ReverseGeocoderCallback callback) {
+
+ Address address = parseAddress(json);
+ if (address != null) {
+ String formattedSpeed = addressFormat.format(address);
+ if (speedcache != null) {
+ speedcache.put(way, formattedSpeed);
+ }
+ if (callback != null) {
+ callback.onSuccess(formattedSpeed);
+ }
+ return formattedSpeed;
+ }
+ return null;
+ }
@Override
public String getAddress(
@@ -111,6 +150,44 @@ public abstract class JsonGeocoder implements Geocoder {
}
return null;
}
+
+ @Override
+ public String getSpeedLimit(
+ final String way, final ReverseGeocoderCallback callback) {
+
+ if (speedcache != null) {
+ String cachedAddress = speedcache.get(way);
+ if (cachedAddress != null) {
+ if (callback != null) {
+ callback.onSuccess(cachedAddress);
+ }
+ return cachedAddress;
+ }
+ }
+
+ Invocation.Builder request = Context.getClient().target(String.format(url, way)).request();
+
+ if (callback != null) {
+ request.async().get(new InvocationCallback<JsonObject>() {
+ @Override
+ public void completed(JsonObject json) {
+ handleSpeedResponse(way, json, callback);
+ }
+
+ @Override
+ public void failed(Throwable throwable) {
+ callback.onFailure(throwable);
+ }
+ });
+ } else {
+ try {
+ return handleSpeedResponse(way, request.get(JsonObject.class), null);
+ } catch (ClientErrorException e) {
+ LOGGER.warn("Geocoder network error", e);
+ }
+ }
+ return null;
+ }
public abstract Address parseAddress(JsonObject json);
diff --git a/src/org/traccar/geocoder/NominatimGeocoder.java b/src/org/traccar/geocoder/NominatimGeocoder.java
index 8db25bf1..94e66a4c 100644
--- a/src/org/traccar/geocoder/NominatimGeocoder.java
+++ b/src/org/traccar/geocoder/NominatimGeocoder.java
@@ -16,14 +16,33 @@
package org.traccar.geocoder;
import javax.json.JsonObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
public class NominatimGeocoder extends JsonGeocoder {
+ private static final Logger LOGGER = LoggerFactory.getLogger(NominatimGeocoder.class);
+
private static String formatUrl(String url, String key, String language) {
if (url == null) {
- url = "https://nominatim.openstreetmap.org/reverse";
+ url = "https://nominatim.openstreetmap.org/";
+ }
+ url += "reverse?format=json&lat=%f&lon=%f&zoom=18&addressdetails=1";
+ if (key != null) {
+ url += "&key=" + key;
+ }
+ if (language != null) {
+ url += "&accept-language=" + language;
+ }
+ return url;
+ }
+
+ private static String formatSpeedUrl(String url, String key, String language) {
+ if (url == null) {
+ url = "https://nominatim.openstreetmap.org/";
}
- url += "?format=json&lat=%f&lon=%f&zoom=18&addressdetails=1";
+ url += "details?format=json&place_id=%f";
if (key != null) {
url += "&key=" + key;
}
@@ -34,16 +53,31 @@ public class NominatimGeocoder extends JsonGeocoder {
}
public NominatimGeocoder(String url, String key, String language, int cacheSize, AddressFormat addressFormat) {
- super(formatUrl(url, key, language), cacheSize, addressFormat);
+ super(formatUrl(url, key, language), cacheSize, addressFormat, formatSpeedUrl(url,key,language));
+ }
+
+ public static void printJsonObject(JsonObject jsonObj) {
+ for (Object key : jsonObj.keySet()) {
+ String keyStr = (String) key;
+ Object keyvalue = jsonObj.get(keyStr);
+ LOGGER.info("NOMINATIM: key: " + keyStr + " value: " + keyvalue);
+ if (keyvalue instanceof JsonObject) {
+ LOGGER.info("NOMINATIM: Recursed:");
+ printJsonObject((JsonObject) keyvalue);
+ LOGGER.info("NOMINATIM: End Recursed.");
+ }
+ }
}
ic Address parseAddress(JsonObject json) {
+
JsonObject result = json.getJsonObject("address");
if (result != null) {
+ LOGGER.info("Geocode called.");
Address address = new Address();
-
+ printJsonObject(result);
if (json.containsKey("display_name")) {
address.setFormattedAddress(json.getString("display_name"));
}
@@ -81,6 +115,9 @@ public class NominatimGeocoder extends JsonGeocoder {
if (result.containsKey("postcode")) {
address.setPostcode(result.getString("postcode"));
}
+ if (result.containsKey("place_id")) {
+ address.setWay(result.getString("place_id"));
+ }
return address;
}
@Override
public Address parseAddress(JsonObject json) {
+
JsonObject result = json.getJsonObject("address");
if (result != null) {
+ LOGGER.info("Geocode called.");
Address address = new Address();
-
+ printJsonObject(result);
if (json.containsKey("display_name")) {
address.setFormattedAddress(json.getString("display_name"));
}
@@ -81,6 +115,9 @@ public class NominatimGeocoder extends JsonGeocoder {
if (result.containsKey("postcode")) {
address.setPostcode(result.getString("postcode"));
}
+ if (result.containsKey("place_id")) {
+ address.setWay(result.getString("place_id"));
+ }
return address;
}
Pull request submitted:
I've noticed a few requests for per-road speed limits and no implementation has surfaced as of yet, so I am working on this now.
To start with I am only implementing this with Nominatim Geocoder (extends jsonGeocoder).
I am extending Geocoder->Address with a speedLimit property.
Handler->Events->OverspeedEventHandler is being extended to use the following priority:
Address Limit (if available) -> Geofence Limit (if available) -> Global Limit
I welcome any suggestions or feedback before I get too dug in.