From e4f667e745953015e0ebb8edd194f0496c4a1a82 Mon Sep 17 00:00:00 2001 From: Jody Marca Date: Wed, 12 Oct 2016 17:31:27 +0200 Subject: [PATCH] New input and output format defined in GeoJson as Feature set --- .../barefoot/matcher/MatcherKState.java | 79 +++++++++++++ .../barefoot/matcher/MatcherServer.java | 70 ++++++++++++ .../barefoot/matcher/ServerControl.java | 10 +- .../barefoot/road/RoadOutputPath.java | 84 ++++++++++++++ .../com/bmwcarit/barefoot/roadmap/Route.java | 108 ++++++++++++++++++ 5 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/bmwcarit/barefoot/road/RoadOutputPath.java diff --git a/src/main/java/com/bmwcarit/barefoot/matcher/MatcherKState.java b/src/main/java/com/bmwcarit/barefoot/matcher/MatcherKState.java index 3ce395db..64e3ff96 100644 --- a/src/main/java/com/bmwcarit/barefoot/matcher/MatcherKState.java +++ b/src/main/java/com/bmwcarit/barefoot/matcher/MatcherKState.java @@ -13,13 +13,21 @@ package com.bmwcarit.barefoot.matcher; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Iterator; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.bmwcarit.barefoot.markov.KState; +import com.bmwcarit.barefoot.road.BaseRoad; +import com.bmwcarit.barefoot.road.Heading; import com.bmwcarit.barefoot.roadmap.Road; import com.bmwcarit.barefoot.roadmap.RoadMap; +import com.bmwcarit.barefoot.road.RoadOutputPath; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.Polyline; import com.esri.core.geometry.WktExportFlags; @@ -156,6 +164,77 @@ public JSONArray toSlimJSON() throws JSONException { } return json; } + + + /** + * Gets {@link JSONObject} with GeoJSON FeatureCollection format of {@link MatcherKState} matched geometries. + * + * @return {@link JSONObject} with GeoJSON FeatureCollection format of {@link MatcherKState} matched geometries. + * @throws JSONException thrown on JSON extraction or parsing error. + */ + public JSONObject toGeoJSONFeatures() throws JSONException { + + + JSONObject json = new JSONObject(); + json.put("type", "FeatureCollection"); + + JSONObject jsonFeature = null; + JSONObject jsonFeatureProperties = null; + MatcherCandidate candidate = null; + JSONArray jsonsequenceFeatures = new JSONArray(); + Polyline localGeometry = null; + Hashtable roadOutputPathList = null; + BaseRoad baseRoad = null; + ArrayListkeysCollections = null; + Integer pathIndex = null; + Integer featuresIndex = 1; + Iterator keyIterator = null; + Heading heading = null; + json.put("features", jsonsequenceFeatures); + + if (this.sequence() != null) { + for (int i = 0; i < this.sequence().size(); ++i) { + candidate = this.sequence().get(i); + + if (candidate.transition() != null) { + + roadOutputPathList = candidate.transition().route().geometryList(); + keysCollections = new ArrayList(roadOutputPathList.keySet()); + Collections.sort(keysCollections); + keyIterator = keysCollections.iterator(); + while(keyIterator.hasNext()){ + pathIndex = keyIterator.next(); + localGeometry = roadOutputPathList.get(pathIndex).getGeometry(); + baseRoad = roadOutputPathList.get(pathIndex).getBase(); + heading = roadOutputPathList.get(pathIndex).getHeading(); + if(localGeometry!=null){ + jsonFeature = new JSONObject(); + jsonFeature.put("type", "Feature"); + jsonFeature.put("geometry", new JSONObject(GeometryEngine.geometryToGeoJson(localGeometry))); + + jsonFeatureProperties = new JSONObject(); + jsonFeatureProperties.put("id", featuresIndex); + featuresIndex++; + + jsonFeatureProperties.put("roadid", baseRoad.refid()); + jsonFeatureProperties.put("time", this.samples().get(i).time() / 1000); + jsonFeatureProperties.put("oneway", baseRoad.oneway()); + jsonFeatureProperties.put("maxspeed", baseRoad.maxspeed(heading)); + jsonFeatureProperties.put("heading", heading.toString()); + + jsonFeature.put("properties", jsonFeatureProperties); + jsonsequenceFeatures.put(jsonFeature); + } + } + + } + + } + + } + + return json; + } private Polyline monitorRoute(MatcherCandidate candidate) { Polyline routes = new Polyline(); diff --git a/src/main/java/com/bmwcarit/barefoot/matcher/MatcherServer.java b/src/main/java/com/bmwcarit/barefoot/matcher/MatcherServer.java index 1cebfb6b..33b1287b 100644 --- a/src/main/java/com/bmwcarit/barefoot/matcher/MatcherServer.java +++ b/src/main/java/com/bmwcarit/barefoot/matcher/MatcherServer.java @@ -39,6 +39,7 @@ import com.bmwcarit.barefoot.topology.Dijkstra; import com.bmwcarit.barefoot.util.AbstractServer; import com.bmwcarit.barefoot.util.Stopwatch; +import com.esri.core.geometry.Point; /** * Matcher server (stand-alone) for Hidden Markov Model offline map matching. It is a @@ -94,6 +95,56 @@ public List format(String input) { } } + + /** + * Input formatter for writing map matched positions, represented be road id and fraction, and + * the geometry of the routes into a GeoJSON FeatureCollection format response message. + */ + public static class GeoJSONFeaturesInputFormatter extends InputFormatter { + @Override + public List format(String input) { + List samples = new LinkedList(); + try{ + Object jsoninput = new JSONTokener(input).nextValue(); + JSONArray jsonsamples = null; + JSONArray coordinates = null; + Point point = null; + String id = null; + Long time = null; + + + + if (jsoninput instanceof JSONObject) { + jsonsamples = ((JSONObject) jsoninput).getJSONArray("features"); + } else { + jsonsamples = ((JSONArray) jsoninput); + } + + + Set times = new HashSet(); + for (int i = 0; i < jsonsamples.length(); ++i) { + coordinates = jsonsamples.getJSONObject(i).getJSONObject("geometry").getJSONArray("coordinates"); + point = new Point(coordinates.getDouble(0), coordinates.getDouble(1)); + id = jsonsamples.getJSONObject(i).getJSONObject("properties").getString("id"); + time = jsonsamples.getJSONObject(i).getJSONObject("properties").getLong("time"); + MatcherSample sample = new MatcherSample(id, time, point); + samples.add(sample); + if (times.contains(sample.time())) { + throw new RuntimeException("multiple samples for same time"); + } else { + times.add(sample.time()); + } + } + + + } catch (JSONException e) { + e.printStackTrace(); + throw new RuntimeException("parsing JSON request: " + e.getMessage()); + } + return samples; + } + } + /** * Default output formatter for writing the JSON representation of a {@link KState} object with * map matching of the input into a response message. @@ -130,6 +181,7 @@ public String format(String request, MatcherKState output) { } } + /** * Output formatter for writing the geometries of a map matched paths into GeoJSON response * message. @@ -144,6 +196,22 @@ public String format(String request, MatcherKState output) { } } } + + + /** + * Output formatter for writing the geometries of a map matched paths into + * GeoJSON FeatureCollection format response message. + */ + public static class GeoJSONFeaturesOutputFormatter extends OutputFormatter { + @Override + public String format(String request, MatcherKState output) { + try { + return output.toGeoJSONFeatures().toString(); + } catch (JSONException e) { + throw new RuntimeException("creating GeoJSONFeatures response"); + } + } + } /** * Output formatter for writing extensive format of input and output of map matching in a JSON @@ -182,6 +250,8 @@ public String format(String request, MatcherKState output) { return new SlimJSONOutputFormatter().format(request, output); case "geojson": return new GeoJSONOutputFormatter().format(request, output); + case "geojsonfeatures": + return new GeoJSONFeaturesOutputFormatter().format(request, output); case "debug": return new DebugJSONOutputFormatter().format(request, output); default: diff --git a/src/main/java/com/bmwcarit/barefoot/matcher/ServerControl.java b/src/main/java/com/bmwcarit/barefoot/matcher/ServerControl.java index 3523214d..55343a55 100644 --- a/src/main/java/com/bmwcarit/barefoot/matcher/ServerControl.java +++ b/src/main/java/com/bmwcarit/barefoot/matcher/ServerControl.java @@ -22,6 +22,8 @@ import org.slf4j.LoggerFactory; import com.bmwcarit.barefoot.matcher.MatcherServer.DebugJSONOutputFormatter; +import com.bmwcarit.barefoot.matcher.MatcherServer.GeoJSONFeaturesOutputFormatter; +import com.bmwcarit.barefoot.matcher.MatcherServer.GeoJSONFeaturesInputFormatter; import com.bmwcarit.barefoot.matcher.MatcherServer.GeoJSONOutputFormatter; import com.bmwcarit.barefoot.matcher.MatcherServer.InputFormatter; import com.bmwcarit.barefoot.matcher.MatcherServer.OutputFormatter; @@ -126,7 +128,7 @@ public static void stopServer() { public static void main(String[] args) { if (args.length < 2 || args.length > 3) { logger.error( - "missing arguments\nusage: [--slimjson|--debug|--geojson] /path/to/server/properties /path/to/mapserver/properties"); + "missing arguments\nusage: [--slimjson|--debug|--geojson|--geojsonfeatures] [--geojsonfeaturesinput] /path/to/server/properties /path/to/mapserver/properties"); System.exit(1); } @@ -145,6 +147,12 @@ public static void main(String[] args) { case "--geojson": output = new GeoJSONOutputFormatter(); break; + case "--geojsonfeatures": + output = new GeoJSONFeaturesOutputFormatter(); + break; + case "--geojsonfeaturesinput": + input = new GeoJSONFeaturesInputFormatter(); + break; default: logger.warn("invalid option {} ignored", args[i]); break; diff --git a/src/main/java/com/bmwcarit/barefoot/road/RoadOutputPath.java b/src/main/java/com/bmwcarit/barefoot/road/RoadOutputPath.java new file mode 100644 index 00000000..7e2613ce --- /dev/null +++ b/src/main/java/com/bmwcarit/barefoot/road/RoadOutputPath.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 + * + * Author: Jody Marca + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in + * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package com.bmwcarit.barefoot.road; + +import com.bmwcarit.barefoot.road.BaseRoad; +import com.bmwcarit.barefoot.road.Heading; +import com.esri.core.geometry.Polyline; + +/** + * RoadOutputPath data structure for a openpenstreetmap road segment. + * + * Provade a geometry (subset or equals to openpenstreetmap geometry) and + * a link to {@link BaseRoad} with relative {@link Heading} + */ +public class RoadOutputPath { + + private BaseRoad base = null; + private Polyline geometry = null; + private Heading heading = null; + + /** + * Constructs {@link RoadOutputPath} object. + * + * @param base {@link BaseRoad} object associated + * @param geometry It's a part of Road geometry as {@link Polyline} object. + * @param heading {@link Heading} needed for speed computation + */ + public RoadOutputPath(BaseRoad base, Polyline geometry, Heading heading) { + this.base = base; + this.geometry = geometry; + this.heading = heading; + } + + /** + * Gets {@link BaseRoad} object associated + * @return baseRoad object associated + */ + public BaseRoad getBase() { + return base; + } + + /** + * Gets road's geometry as a {@link Polyline} as part of Road geometry. + * + * @return Road's geometry as {@link Polyline} as part of Road geometry. + */ + public Polyline getGeometry() { + return geometry; + } + + /** + * Gets the {@link BaseRoad} identifier if presents + * + * @return baseroad identifier or null + */ + public Long getId(){ + if(base == null){ + return null; + }else{ + return base.refid(); + } + } + + /** + * Gets {@link Heading} needed for speed computation + * @return heading associated to the instance + */ + public Heading getHeading() { + return heading; + } + + +} diff --git a/src/main/java/com/bmwcarit/barefoot/roadmap/Route.java b/src/main/java/com/bmwcarit/barefoot/roadmap/Route.java index 0b7c7292..1c315d49 100755 --- a/src/main/java/com/bmwcarit/barefoot/roadmap/Route.java +++ b/src/main/java/com/bmwcarit/barefoot/roadmap/Route.java @@ -13,6 +13,7 @@ package com.bmwcarit.barefoot.roadmap; +import java.util.Hashtable; import java.util.LinkedList; import java.util.List; @@ -20,6 +21,7 @@ import org.json.JSONException; import org.json.JSONObject; +import com.bmwcarit.barefoot.road.RoadOutputPath; import com.bmwcarit.barefoot.spatial.Geography; import com.bmwcarit.barefoot.spatial.SpatialOperator; import com.bmwcarit.barefoot.topology.Path; @@ -200,6 +202,112 @@ public Polyline geometry() { return geometry; } + + + /** + * Gets geometry list of the {@link Route} from start point to end point. + * The division of geometries is based on openstreetmap road ids + * + * @return Geometry of the route. + */ + public Hashtable geometryList() { + Hashtable result = new Hashtable(); + + Polyline geometry = new Polyline(); + Point lastPoint = null; + RoadOutputPath roadOutputPath = null; + Road baseRoad = source().edge(); + geometry.startPath(source().geometry()); + roadOutputPath = new RoadOutputPath(baseRoad.base(), geometry, baseRoad.heading()); + + result.put(result.size()+1,roadOutputPath); + + if (source().edge().id() != target().edge().id()) { + { + double f = source().edge().length() * source().fraction(), s = 0; + Point a = source().edge().geometry().getPoint(0); + + for (int i = 1; i < source().edge().geometry().getPointCount(); ++i) { + Point b = source().edge().geometry().getPoint(i); + s += spatial.distance(a, b); + a = b; + + if (s <= f) { + continue; + } + + geometry.lineTo(b); + } + } + for (int i = 1; i < path().size() - 1; ++i) { + Polyline segment = path().get(i).geometry(); + baseRoad = path().get(i); + if(baseRoad.base()!=null&&baseRoad.base().refid()!=roadOutputPath.getId()&&geometry.getPointCount()>0){ + lastPoint = geometry.getPoint(geometry.getPointCount()-1); + geometry = new Polyline(); + geometry.startPath(lastPoint); + roadOutputPath = new RoadOutputPath(baseRoad.base(), geometry, baseRoad.heading()); + result.put(result.size()+1,roadOutputPath); + } + + for (int j = 1; j < segment.getPointCount(); ++j) { + geometry.lineTo(segment.getPoint(j)); + } + } + { + double f = target().edge().length() * target().fraction(), s = 0; + Point a = target().edge().geometry().getPoint(0); + baseRoad = target().edge(); + + if(baseRoad.base()!=null&&baseRoad.base().refid()!=roadOutputPath.getId()){ + lastPoint = geometry.getPoint(geometry.getPointCount()-1); + geometry = new Polyline(); + geometry.startPath(lastPoint); + roadOutputPath = new RoadOutputPath(baseRoad.base(), geometry, baseRoad.heading()); + result.put(result.size()+1,roadOutputPath); + } + + for (int i = 1; i < target().edge().geometry().getPointCount() - 1; ++i) { + Point b = target().edge().geometry().getPoint(i); + s += spatial.distance(a, b); + a = b; + + if (s >= f) { + + geometry.startPath(target().edge().geometry().getPoint(i-1)); + break; + } + + geometry.lineTo(b); + } + } + } else { + double sf = source().edge().length() * source().fraction(); + double tf = target().edge().length() * target().fraction(); + double s = 0; + Point a = source().edge().geometry().getPoint(0); + + for (int i = 1; i < source().edge().geometry().getPointCount() - 1; ++i) { + Point b = source().edge().geometry().getPoint(i); + s += spatial.distance(a, b); + a = b; + + if (s <= sf) { + continue; + } + if (s >= tf) { + break; + } + + geometry.lineTo(b); + } + } + + geometry.lineTo(target().geometry()); + + return result; + } + /** * Creates a {@link Route} object from its JSON representation.