bug5451: added getLocation() on Event and use it during export; link regatta to event

This commit is contained in:
Axel Uhl
2021-07-06 12:16:54 +02:00
parent 7b4da25a83
commit 6fcd7bcfa0
8 changed files with 82 additions and 45 deletions
@@ -9,26 +9,29 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Placemark;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
import com.sap.sailing.domain.tracking.impl.MarkPositionAtTimePointCacheImpl;
import com.sap.sailing.geocoding.ReverseGeocoder;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
public class EventImpl extends EventBaseImpl implements Event {
private static final long serialVersionUID = 855135446595485715L;
private static final Logger logger = Logger.getLogger(EventImpl.class.getName());
private ConcurrentLinkedQueue<LeaderboardGroup> leaderboardGroups;
@@ -132,13 +135,15 @@ public class EventImpl extends EventBaseImpl implements Event {
final Set<Position> positionSamples = new HashSet<>();
for (final LeaderboardGroup leaderboardGroup : getLeaderboardGroups()) {
for (final Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
for (final TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
if (trackedRace.getStartOfRace() != null) {
final Position centerOfCourse = trackedRace.getCenterOfCourse(trackedRace.getStartOfRace());
if (centerOfCourse != null) {
positionSamples.add(centerOfCourse);
if (positionSamples.size() >= MAX_NUMBER_OF_POSITION_SAMPLES) {
return getAveragePosition(positionSamples);
if (Util.containsAny(leaderboard.getCourseAreas(), getVenue().getCourseAreas())) {
for (final TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
if (trackedRace.getStartOfRace() != null) {
final Position centerOfCourse = trackedRace.getCenterOfCourse(trackedRace.getStartOfRace());
if (centerOfCourse != null) {
positionSamples.add(centerOfCourse);
if (positionSamples.size() >= MAX_NUMBER_OF_POSITION_SAMPLES) {
return getAveragePosition(positionSamples);
}
}
}
}
@@ -147,8 +152,15 @@ public class EventImpl extends EventBaseImpl implements Event {
}
// try to geo-code the venue:
final ReverseGeocoder geocoder = ReverseGeocoder.INSTANCE;
// TODO enhance ReverseGeocoder by the use of the forward search, such as http://api.geonames.org/search?name=Kiel&username=sailtracking3, then use Placemark.ByPopulation to sort
return null;
try {
final Placemark placemark = geocoder.getPlacemark(getVenue().getName(), new Placemark.ByPopulation().reversed());
if (placemark != null) {
positionSamples.add(placemark.getPosition());
}
} catch (IOException | ParseException e) {
logger.log(Level.WARNING, "Problem while trying to resolve venue name "+getVenue().getName()+" with geocoder", e);
}
return positionSamples.isEmpty() ? null : getAveragePosition(positionSamples);
}
private Position getAveragePosition(Set<Position> positionSamples) {
@@ -1,11 +1,14 @@
package com.sap.sailing.geocoding.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.List;
import org.json.simple.parser.ParseException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -16,8 +19,6 @@ import com.sap.sailing.domain.common.impl.PlacemarkImpl;
import com.sap.sailing.geocoding.ReverseGeocoder;
import com.sap.sailing.geocoding.impl.ReverseGeocoderImpl;
import org.junit.Assert;
public class ReverseGeocoderTest {
private ReverseGeocoder geocoder;
private static final Placemark KIEL = new PlacemarkImpl("Kiel", "DE", new DegreePosition(54.32132926107913, 10.1348876953125), 232758);
@@ -28,6 +29,14 @@ public class ReverseGeocoderTest {
geocoder = new ReverseGeocoderImpl(); // ensure we don't see any caching effects across test case executions
}
@Test
public void getByNameTest() throws IOException, ParseException {
final Placemark kiel = geocoder.getPlacemark("Kiel", new Placemark.ByPopulation().reversed());
assertNotNull(kiel);
assertTrue(kiel.getPopulation() > 200000);
assertEquals("DE", kiel.getCountryCode());
}
@Test
public void getPlacemarkSimpleTest() {
//Simple Test in Kiel center to check the connection and the parsing from JSONObject to Placemark
@@ -9,5 +9,6 @@ Bundle-ActivationPolicy: lazy
Export-Package: com.sap.sailing.geocoding
Require-Bundle: org.json.simple;bundle-version="1.1.0",
com.sap.sailing.domain.common,
com.sap.sse.common
com.sap.sse.common,
com.sap.sse
Automatic-Module-Name: com.sap.sailing.geocoding
@@ -66,4 +66,10 @@ public interface ReverseGeocoder {
*/
Placemark getPlacemarkFirst(Position position, double radius, Comparator<Placemark> comp) throws IOException,
ParseException;
/**
* Actually not a "reverse" geocoding but a name search that emits placemarks sorted with the comparator
* and returning the first element in the sorting order or {@code null} if the result is empty.
*/
Placemark getPlacemark(String name, Comparator<Placemark> comp) throws IOException, ParseException;
}
@@ -6,6 +6,7 @@ import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
@@ -25,11 +26,15 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.PlacemarkImpl;
import com.sap.sailing.domain.common.quadtree.QuadTree;
import com.sap.sailing.geocoding.ReverseGeocoder;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.util.HttpUrlConnectionHelper;
public class ReverseGeocoderImpl implements ReverseGeocoder {
private final String NEARBY_PLACE_SERVICE = "http://api.geonames.org/findNearbyPlaceNameJSON?";
private static final String BASE_URL = "http://api.geonames.org";
private static final String NEARBY_PLACE_SERVICE = BASE_URL+"/findNearbyPlaceNameJSON?";
private static final String SEARCH_BY_NAME_SERVICE = BASE_URL+"/searchJSON?";
/**
* Maximal distance in degree for the cache.<br />
* The first number is the distance in kilometers and the second number is a needed calculation factor and mustn't
@@ -48,14 +53,12 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
public Placemark getPlacemarkNearest(Position position) throws IOException, ParseException {
Placemark p = null;
Util.Triple<Position, Double, List<Placemark>> cachedPlacemarks = checkCache(position);
if (cachedPlacemarks != null && cachedPlacemarks.getC() != null && !cachedPlacemarks.getC().isEmpty()) {
p = cachedPlacemarks.getC().get(0);
} else {
JSONArray geonames = callNearestService(position);
if (geonames != null && !geonames.isEmpty()) {
p = JSONToPlacemark((JSONObject) geonames.get(0));
p = jsonToPlacemark((JSONObject) geonames.get(0));
if (p != null) {
List<Placemark> placemarks = new ArrayList<Placemark>();
placemarks.add(p);
@@ -63,7 +66,6 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
}
}
}
return p;
}
@@ -71,14 +73,12 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
public List<Placemark> getPlacemarksNear(Position position, double radius) throws IOException, ParseException {
List<Placemark> placemarks = null;
Util.Triple<Position, Double, List<Placemark>> cachedPlacemarks = checkCache(position);
//Calculating the search radius and the maximum number of returning Placemarks
// Calculating the search radius and the maximum number of returning Placemarks
double limitedRadius = Math.min(radius, MAX_RADIUS);
int radiusInt = (int) limitedRadius;
int xKmRadius = radiusInt / XKM_RADIUS;
int maxRows = (int) (ROWS_PER_XKM_RADIUS * Math.pow(2, xKmRadius));
maxRows = Math.min(maxRows, MAX_ROW_NUMBER);
if (cachedPlacemarks != null && cachedPlacemarks.getB() >= limitedRadius) {
if (cachedPlacemarks.getC().size() > maxRows) {
placemarks = cachedPlacemarks.getC().subList(0, maxRows);
@@ -86,7 +86,7 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
placemarks = new ArrayList<Placemark>(cachedPlacemarks.getC());
}
} else {
//Recalculating the radius and resetting the search position to keep the cache correct
// Recalculating the radius and resetting the search position to keep the cache correct
Position searchPosition = null;
if (cachedPlacemarks != null) {
searchPosition = cachedPlacemarks.getA();
@@ -95,16 +95,15 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
} else {
searchPosition = position;
}
JSONArray geonames = callNearbyService(searchPosition, limitedRadius, maxRows);
if (geonames != null) {
Iterator<Object> iterator = geonames.iterator();
placemarks = iterator.hasNext() ? new ArrayList<Placemark>() : null;
while (iterator.hasNext()) {
JSONObject object = (JSONObject) iterator.next();
Placemark place = JSONToPlacemark(object);
Placemark place = jsonToPlacemark(object);
if (place != null) {
placemarks.add(JSONToPlacemark(object));
placemarks.add(jsonToPlacemark(object));
}
}
}
@@ -115,7 +114,6 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
updateCachedPlacemarks(searchPosition, limitedRadius, placemarks);
}
}
return placemarks;
}
@@ -132,6 +130,18 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
List<Placemark> placemarks = getPlacemarksNearSorted(position, radius, comp);
return placemarks == null ? null : placemarks.get(0);
}
@Override
public Placemark getPlacemark(String name, Comparator<Placemark> comp) throws IOException, ParseException {
StringBuilder url = new StringBuilder(SEARCH_BY_NAME_SERVICE);
url.append("name=" + URLEncoder.encode(name, "UTF-8"));
URLConnection connection = addUsernameParameterAndConnect(url);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));
final JSONParser parser = new JSONParser();
final JSONObject obj = (JSONObject) parser.parse(in);
final JSONArray geonames = (JSONArray) obj.get("geonames");
return geonames.stream().map(o->jsonToPlacemark((JSONObject) o)).sorted(comp).findFirst().orElse(null);
}
private List<Placemark> getPlacemarksNearSorted(Position position, double radius, Comparator<Placemark> comp)
throws IOException, ParseException {
@@ -150,10 +160,9 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
* @return A {@link Placemark} or <code>null</code>, if the object doesn't contain a name, a postion or the
* if the population is 0
*/
private Placemark JSONToPlacemark(JSONObject json) {
private Placemark jsonToPlacemark(JSONObject json) {
String name = (String) json.get("toponymName");
String countryCode = (String) json.get("countryCode");
// Tries are necessary, because some latitude or longitude values delivered by Geonames have no decimal places
// and are interpreted as Long
// Casting a Long to a Double raises a ClassCastException
@@ -234,12 +243,9 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
private JSONArray callNearestService(Position position) throws MalformedURLException, IOException, ParseException {
StringBuilder url = new StringBuilder(NEARBY_PLACE_SERVICE);
url.append("&lat=" + Double.toString(position.getLatDeg()));
url.append("lat=" + Double.toString(position.getLatDeg()));
url.append("&lng=" + Double.toString(position.getLngDeg()));
url.append("&username=" + getGeonamesUser());
URL request = new URL(url.toString());
URLConnection connection = request.openConnection();
URLConnection connection = addUsernameParameterAndConnect(url);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));
JSONParser parser = new JSONParser();
JSONObject obj = (JSONObject) parser.parse(in);
@@ -247,18 +253,20 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
return geonames;
}
private URLConnection addUsernameParameterAndConnect(StringBuilder url) throws MalformedURLException, IOException {
url.append("&username=");
url.append(getGeonamesUser());
return HttpUrlConnectionHelper.redirectConnection(new URL(url.toString()), Duration.ONE_MINUTE, c->c.setRequestProperty("User-Agent", ""));
}
private JSONArray callNearbyService(Position position, double radius, int maxRows) throws MalformedURLException,
IOException, ParseException {
StringBuilder url = new StringBuilder(NEARBY_PLACE_SERVICE);
url.append("&lat=" + Double.toString(position.getLatDeg()));
url.append("lat=" + Double.toString(position.getLatDeg()));
url.append("&lng=" + Double.toString(position.getLngDeg()));
url.append("&radius=" + Double.toString(radius));
url.append("&maxRows=" + Integer.toString(maxRows));
url.append("&username=" + getGeonamesUser());
URL request = new URL(url.toString());
URLConnection connection = request.openConnection();
connection.setRequestProperty("User-Agent", "");
URLConnection connection = addUsernameParameterAndConnect(url);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));
JSONParser parser = new JSONParser();
JSONObject obj = (JSONObject) parser.parse(in);
@@ -270,5 +278,4 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
private String getGeonamesUser() {
return GEONAMES_USER+new Random().nextInt(10);
}
}
@@ -30,7 +30,7 @@ CREATE COLUMN TABLE SAILING."Regatta" (
"boatClass" NVARCHAR(20) NOT NULL,
"scoringScheme" NVARCHAR(255) NOT NULL,
"rankingMetric" NVARCHAR(255) NOT NULL,
"eventId" NVARCHAR(36)
"eventId" NVARCHAR(36),
FOREIGN KEY ("boatClass") REFERENCES SAILING."BoatClass" ("id"),
FOREIGN KEY ("eventId") REFERENCES SAILING."Event" ("id"),
FOREIGN KEY ("scoringScheme") REFERENCES SAILING."ScoringScheme" ("id")
@@ -32,6 +32,7 @@ import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -158,7 +159,8 @@ public class HanaCloudSacExportResource extends SharedAbstractSailingServerResou
insertEvents.setString(5, event.getVenue().getName());
insertEvents.setBoolean(6, event.isPublic());
insertEvents.setString(7, event.getDescription());
insertEvents.setString(8, "POINT(49.5 -2.4)");
final Position location = event.getLocation();
insertEvents.setString(8, location != null ? String.format("POINT(%1.5f %1.5f)", location.getLatDeg(), location.getLngDeg()) : null);
insertEvents.execute();
}
}
@@ -107,7 +107,7 @@ public class HttpUrlConnectionHelper {
}
urlConnection.setDoOutput(true);
if (optionalRequestMethod != null) {
((HttpURLConnection)urlConnection).setRequestMethod(optionalRequestMethod);
((HttpURLConnection) urlConnection).setRequestMethod(optionalRequestMethod);
}
urlConnection.setReadTimeout((int) timeout.asMillis());
if (urlConnection instanceof HttpURLConnection) {