/* * Copyright 2013 Serdar. * * 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 de.fub.maps.project.plugins.tasks.eval; import de.fub.agg2graph.osm.OsmExporter; import de.fub.agg2graph.roadgen.RoadNetwork; import de.fub.agg2graph.structs.GPSPoint; import de.fub.agg2graph.structs.GPSSegment; import de.fub.agg2graph.structs.ILocation; import de.fub.agg2graph.ui.gui.RenderingOptions; import de.fub.agg2graphui.layers.GPSSegmentLayer; import de.fub.agg2graphui.layers.Line; import de.fub.agg2graphui.layers.MapMatchingLayer; import de.fub.maps.project.aggregator.factories.nodes.properties.ClassProperty; import de.fub.maps.project.aggregator.factories.nodes.properties.ClassWrapper; import de.fub.maps.project.aggregator.pipeline.AbstractAggregationProcess; import de.fub.maps.project.aggregator.pipeline.AggregationProcessNode; import de.fub.maps.project.aggregator.xml.ProcessDescriptor; import de.fub.maps.project.aggregator.xml.Property; import de.fub.maps.project.aggregator.xml.PropertySection; import de.fub.maps.project.aggregator.xml.PropertySet; import de.fub.maps.project.api.statistics.StatisticProvider; import de.fub.maps.project.openstreetmap.service.MapProvider; import de.fub.maps.project.openstreetmap.xml.osm.Nd; import de.fub.maps.project.openstreetmap.xml.osm.Node; import de.fub.maps.project.openstreetmap.xml.osm.Osm; import de.fub.maps.project.openstreetmap.xml.osm.Way; import de.fub.maps.project.plugins.mapmatcher.MapMatcher; import java.awt.Color; import java.awt.Component; import java.awt.Image; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JComponent; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import org.netbeans.api.annotations.common.StaticResource; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.nodes.Sheet; import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; /** * * @author Serdar */ @ServiceProvider(service = AbstractAggregationProcess.class) public class OSMEvaluatorProcess extends AbstractAggregationProcess<RoadNetwork, RoadNetwork> implements StatisticProvider { @StaticResource private static final String ICON_PATH = "de/fub/maps/project/plugins/tasks/eval/datasourceProcessIcon.png"; private static final String PROP_NAME_MAP_MATCH_INSTANCE = "osm.Evaluator.mapmatching.instance"; private static final String PROP_NAME_MAP_PROVIDER_INSTANCE = "osm.evaluator.mapprovider.instance"; private static final Logger LOG = Logger.getLogger(OSMEvaluatorProcess.class.getName()); private static final Image IMAGE = ImageUtilities.loadImage(ICON_PATH); private final GPSSegmentLayer aggregatorRoadLayer = new GPSSegmentLayer("Aggregator Road Network", new RenderingOptions()); private final GPSSegmentLayer osmRoadLayer = new GPSSegmentLayer("OSM Road Network", new RenderingOptions()); private final MapMatchingLayer matchingLayer = new MapMatchingLayer("Map Matching Layer", new RenderingOptions()); private final GPSSegmentLayer resultLayer = new GPSSegmentLayer("OSM Matched Road Network", new RenderingOptions()); private RoadNetwork roadNetwork; private OSMEvaluatorProcessNode node; private MapMatcher mapMatcher; private MapProvider mapProvider; private double averageDistance; private double mappingCost; public OSMEvaluatorProcess() { osmRoadLayer.getRenderingOptions().setColor(Color.green); getLayers().add(osmRoadLayer); osmRoadLayer.getRenderingOptions().setzIndex(0); aggregatorRoadLayer.getRenderingOptions().setColor(Color.blue); getLayers().add(aggregatorRoadLayer); matchingLayer.getRenderingOptions().setColor(Color.red); getLayers().add(matchingLayer); resultLayer.getRenderingOptions().setColor(Color.cyan); getLayers().add(resultLayer); } @Override public void setProcessDescriptor(ProcessDescriptor processDescriptor) { super.setProcessDescriptor(processDescriptor); reInit(); } private void reInit() { if (getProcessDescriptor() != null) { List<PropertySection> sections = getProcessDescriptor().getProperties().getSections(); for (PropertySection section : sections) { if (OSMEvaluatorProcess.class.getName().equals(section.getId())) { List<PropertySet> propertySets = section.getPropertySet(); for (PropertySet propertySet : propertySets) { if (OSMEvaluatorProcess.class.getName().equals(propertySet.getId())) { List<Property> properties = propertySet.getProperties(); for (Property property : properties) { if (property.getValue() != null) { if (PROP_NAME_MAP_MATCH_INSTANCE.equals(property.getId())) { try { mapMatcher = MapMatcher.Factory.find(property.getValue()); } catch (MapMatcher.MapMatcherNotFoundException ex) { Exceptions.printStackTrace(ex); } if (mapMatcher == null) { mapMatcher = MapMatcher.Factory.getDefault(); } } else if (PROP_NAME_MAP_PROVIDER_INSTANCE.equals(property.getId())) { try { mapProvider = MapProvider.Factory.find(property.getValue()); } catch (MapProvider.MapProviderNotFoundException ex) { Exceptions.printStackTrace(ex); } if (mapProvider == null) { mapProvider = MapProvider.Factory.getDefault(); } } } } } } } } } } public double getAvgMappingDistance() { return averageDistance; } public double getMappingCost() { return mappingCost; } public RoadNetwork getRoadNetwork() { return roadNetwork; } @Override protected void start() { // clear layers aggregatorRoadLayer.clearRenderObjects(); osmRoadLayer.clearRenderObjects(); matchingLayer.clearRenderObjects(); resultLayer.clearRenderObjects(); if (roadNetwork != null) { RoadNetwork network = roadNetwork; ProgressHandle handle = ProgressHandleFactory.createHandle(getName()); handle.start(); try { // convert specified roadnetwork to osm format // its necessary because through the export of the road network to // osm format the roadnetwork gets simplified Osm roadNetworkToOsm = convertRoadNetworkToOsm(); if (roadNetworkToOsm != null) { // convert osm to a more convenient respresentation List<GPSSegment> roadGPSSegmentList = convertOsmToGPSSegments(roadNetworkToOsm); addGPSSegmentsToLayer(roadGPSSegmentList, aggregatorRoadLayer); List<GPSSegment> osmRoadNetwork = new ArrayList<GPSSegment>(); // fetch the osm map that is covered by the gpssegments bounding box // we need this approach, because osm has a 5000 nodes limit per request // we try to minimize the bounding box by using the bounding box // of the segment instead of the whole road network handle.setDisplayName("Downloading map..."); Osm osmMap = getOSMMap(roadGPSSegmentList); if (osmMap != null) { List<GPSSegment> osmGPSSegmentList = convertOsmToGPSSegments(osmMap); if (osmGPSSegmentList != null && !osmGPSSegmentList.isEmpty()) { osmRoadNetwork.addAll(osmGPSSegmentList); addGPSSegmentsToLayer(osmGPSSegmentList, osmRoadLayer); } } if (getMapMatcher() != null && !osmRoadNetwork.isEmpty()) { double cost = -1; double count = 0; double sumDistance = 0; List<MapMatcher.MapMatchSegment> matchedRoadNetwork = getMapMatcher().findMatch(roadGPSSegmentList, osmRoadNetwork); if (matchedRoadNetwork != null && !matchedRoadNetwork.isEmpty()) { for (MapMatcher.MapMatchSegment matchedSegment : matchedRoadNetwork) { // get the average cost of the matched segment cost += matchedSegment.getMapMatchCost(); count++; GPSSegment resultSegment = new GPSSegment(); for (MapMatcher.MapMatchResult match : matchedSegment.getSegment()) { Line line = new Line( match.getTobeMatchedPoint(), match.getMatchedPoint(), matchingLayer.getRenderingOptions(), 1); line.setLabel(String.valueOf(match.getDistance())); sumDistance += match.getDistance(); matchingLayer.add(line); resultSegment.add(new GPSPoint(match.getMatchedPoint())); } resultLayer.add(resultSegment); } } // cost of the map-matching in average distance unit averageDistance = count > 0 ? cost / count : 0; mappingCost = network.getTotalRoadLength() == 0 ? 0 : averageDistance / network.getTotalRoadLength(); LOG.log(Level.INFO, "average distance: {0}", averageDistance); LOG.log(Level.INFO, "average distance/network length: {0}", mappingCost); NotifyDescriptor.Message nd = new NotifyDescriptor.Message( String.format(Locale.ENGLISH, "Map-Matcher cost amounts to %f\nAverage Distance (m): %f\nSum length to networklength: %f", mappingCost, averageDistance, sumDistance / roadNetwork.getTotalRoadLength())); DialogDisplayer.getDefault().notifyLater(nd); } } } finally { handle.finish(); } } } private Osm getOSMMap(List<GPSSegment> roadNetwork) { Rectangle2D boundingBox = getBoundingBox(roadNetwork); double buffer = 0.000000025; double leftLong = boundingBox.getMinX() - buffer; double bottomLat = boundingBox.getMinY() - buffer; double rightLong = boundingBox.getMaxX() + buffer; double topLat = boundingBox.getMaxY() + buffer; Osm osmMap = null; try { osmMap = mapProvider.getMap(leftLong, bottomLat, rightLong, topLat); } catch (Throwable ex) { LOG.log(Level.SEVERE, ex.getMessage(), ex); } return osmMap; } private Rectangle2D getBoundingBox(List<GPSSegment> gpsSegments) { Area area = new Area(); Rectangle2D point = null; for (GPSSegment gpsSegment : gpsSegments) { for (ILocation coordinate : gpsSegment) { point = new Rectangle2D.Double(coordinate.getLon(), coordinate.getLat(), 0.00000001, 0.00000001); area.add(new Area(point)); } } return area.getBounds2D(); } private void addGPSSegmentsToLayer(List<GPSSegment> gpsSegmentList, GPSSegmentLayer layer) { layer.addAll(gpsSegmentList); } private MapMatcher getMapMatcher() { return mapMatcher; } public void setMapMatcher(MapMatcher mapMatcher) { this.mapMatcher = mapMatcher; } public MapProvider getMapProvider() { return mapProvider; } public void setMapProvider(MapProvider mapProvider) { this.mapProvider = mapProvider; } private List<GPSSegment> convertOsmToGPSSegments(Osm osmRoadMap) { List<GPSSegment> gpsSegmentList = new ArrayList<GPSSegment>(500); List<Node> nodeList = osmRoadMap.getNodes(); HashMap<Long, Node> nodeIndexMap = new HashMap<Long, Node>(); for (Node node : nodeList) { nodeIndexMap.put(node.getId(), node); } List<Way> wayList = osmRoadMap.getWays(); GPSPoint point = null; for (Way way : wayList) { GPSSegment waySegment = new GPSSegment(); for (Nd nd : way.getNds()) { if (nodeIndexMap == null || nodeIndexMap.isEmpty()) { nodeIndexMap = new HashMap<Long, Node>(); for (Node node : nodeList) { nodeIndexMap.put(node.getId(), node); } } Node n = nodeIndexMap.get(nd.getRef()); if (n != null) { point = new GPSPoint(n.getLat(), n.getLon()); waySegment.add(point); } } // add non empty segments to segment list. if (!waySegment.isEmpty()) { gpsSegmentList.add(waySegment); } } return gpsSegmentList; } private Osm convertRoadNetworkToOsm() { Osm roadNetworkAsOsm = null; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); OsmExporter exporter = new OsmExporter(); exporter.export(roadNetwork, outputStream); try { outputStream.close(); ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); try { JAXBContext jaxbCtx = JAXBContext.newInstance(Osm.class); Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller(); Object object = unmarshaller.unmarshal(inputStream); //NOI18N if (object instanceof Osm) { roadNetworkAsOsm = (Osm) object; } } catch (javax.xml.bind.JAXBException ex) { LOG.log(Level.SEVERE, ex.getMessage(), ex); //NOI18N } finally { inputStream.close(); } } catch (IOException ex) { LOG.log(Level.SEVERE, ex.getMessage(), ex); //NOI18N } return roadNetworkAsOsm; } @Override public Image getIcon() { return IMAGE; } @Override public JComponent getSettingsView() { return null; } @NbBundle.Messages("CLT_OSMEvaluatorProcess_Name=OSM Map Evaluator") @Override public String getName() { if (getProcessDescriptor() != null) { return getProcessDescriptor().getDisplayName(); } return Bundle.CLT_OSMEvaluatorProcess_Name(); } @NbBundle.Messages("CLT_OSMEvaluatorProcess_Description=A map evaluator which uses osm map to evaluator") @Override public String getDescription() { if (getProcessDescriptor() != null) { return getProcessDescriptor().getDescription(); } return Bundle.CLT_OSMEvaluatorProcess_Description(); } @Override public void setInput(RoadNetwork input) { this.roadNetwork = input; } @Override public RoadNetwork getResult() { RoadNetwork resultRoadNetwork = roadNetwork; roadNetwork = null; return resultRoadNetwork; } @Override public boolean cancel() { canceled.set(true); return canceled.get(); } @Override public List<StatisticSection> getStatisticData() throws StatisticNotAvailableException { List<StatisticSection> statisticData = new ArrayList<StatisticSection>(); // create process performance statistics StatisticSection section = getPerformanceData(); statisticData.add(section); return statisticData; } @Override public Component getVisualRepresentation() { return null; } @NbBundle.Messages({ "CLT_OSMEvaluatorProcess_Property_MapMatcher_Name=Map-Matcher Instance", "CLT_OSMEvaluatorProcess_Property_MapMatcher_Description=The instance that is responsible for the matching of the points", "CLT_OSMEvaluatorProcess_Property_MapProvider_Name=Map-Provider Instance", "CLT_OSMEvaluatorProcess_Property_MapProvider_Description=Different types of map providers for the matching." }) @Override protected ProcessDescriptor createProcessDescriptor() { ProcessDescriptor processDescriptor = new ProcessDescriptor(); processDescriptor.setJavaType(OSMEvaluatorProcess.class.getName()); processDescriptor.setDisplayName(Bundle.CLT_OSMEvaluatorProcess_Name()); processDescriptor.setDescription(Bundle.CLT_OSMEvaluatorProcess_Description()); PropertySet propertySet = new PropertySet(); propertySet.setId(OSMEvaluatorProcess.class.getName()); propertySet.setName("Properties"); propertySet.setDescription("Properties concering the map matcher instance."); Property property = new Property(); property.setId(PROP_NAME_MAP_MATCH_INSTANCE); property.setJavaType(String.class.getName()); property.setName(Bundle.CLT_OSMEvaluatorProcess_Property_MapMatcher_Name()); property.setDescription(Bundle.CLT_OSMEvaluatorProcess_Property_MapMatcher_Description()); property.setValue(MapMatcher.Factory.getDefault().getClass().getName()); propertySet.getProperties().add(property); property = new Property(); property.setId(PROP_NAME_MAP_PROVIDER_INSTANCE); property.setJavaType(String.class.getName()); property.setName(Bundle.CLT_OSMEvaluatorProcess_Property_MapProvider_Name()); property.setDescription(Bundle.CLT_OSMEvaluatorProcess_Property_MapProvider_Description()); property.setValue(MapProvider.Factory.getDefault().getClass().getName()); propertySet.getProperties().add(property); PropertySection propertySection = new PropertySection(); propertySection.setId(OSMEvaluatorProcess.class.getName()); propertySection.setName("Properties"); propertySection.setDescription("Properties of this Evaluator process."); propertySection.getPropertySet().add(propertySet); propertySection.getPropertySet(); processDescriptor.getProperties().getSections().add(propertySection); return processDescriptor; } @Override public org.openide.nodes.Node getNodeDelegate() { if (node == null) { node = new OSMEvaluatorProcessNode(OSMEvaluatorProcess.this); } return node; } private static class OSMEvaluatorProcessNode extends AggregationProcessNode { private final OSMEvaluatorProcess process; public OSMEvaluatorProcessNode(OSMEvaluatorProcess process) { super(process); this.process = process; } @Override protected Sheet createSheet() { Sheet sheet = Sheet.createDefault(); Sheet.Set set = Sheet.createPropertiesSet(); sheet.put(set); if (process != null && process.getProcessDescriptor() != null) { ProcessDescriptor processDescriptor = process.getProcessDescriptor(); for (PropertySection section : processDescriptor.getProperties().getSections()) { if (OSMEvaluatorProcess.class.getName().equals(section.getId())) { for (de.fub.maps.project.aggregator.xml.PropertySet propertySet : section.getPropertySet()) { if (OSMEvaluatorProcess.class.getName().equals(propertySet.getId())) { for (final de.fub.maps.project.aggregator.xml.Property property : propertySet.getProperties()) { if (PROP_NAME_MAP_MATCH_INSTANCE.equals(property.getId())) { ClassProperty classProperty = new MapMatcherProperty(property); set.put(classProperty); } else if (PROP_NAME_MAP_PROVIDER_INSTANCE.equals(property.getId())) { ClassProperty classProperty = new MapProviderProperty(property); set.put(classProperty); } } } } } } } return sheet; } private class MapMatcherProperty extends ClassProperty { private final de.fub.maps.project.aggregator.xml.Property property; private ClassWrapper wrapper = process.getMapMatcher() != null ? new ClassWrapper(process.getMapMatcher().getClass()) : null; public MapMatcherProperty(de.fub.maps.project.aggregator.xml.Property property) { super(property.getId(), property.getName(), property.getDescription(), MapMatcher.class); this.property = property; } @Override public ClassWrapper getValue() throws IllegalAccessException, InvocationTargetException { return wrapper; } @Override public void setValue(ClassWrapper val) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (val == null) { throw new IllegalArgumentException("Null is not a valid value"); } else if (wrapper == null || !val.getQualifiedName().equals(wrapper.getQualifiedName())) { wrapper = val; try { MapMatcher matcher = MapMatcher.Factory.find(val.getQualifiedName()); if (matcher != null) { process.setMapMatcher(matcher); property.setValue(matcher.getClass().getName()); } } catch (MapMatcher.MapMatcherNotFoundException ex) { Exceptions.printStackTrace(ex); } } } } private class MapProviderProperty extends ClassProperty { private final de.fub.maps.project.aggregator.xml.Property property; public MapProviderProperty(de.fub.maps.project.aggregator.xml.Property property) { super(property.getId(), property.getName(), property.getDescription(), MapProvider.class); this.property = property; } private ClassWrapper wrapper = process.getMapProvider() != null ? new ClassWrapper(process.getMapProvider().getClass()) : null; @Override public ClassWrapper getValue() throws IllegalAccessException, InvocationTargetException { return wrapper; } @Override public void setValue(ClassWrapper val) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (val == null) { throw new IllegalArgumentException("Null is not a valid Argument"); } else if (wrapper != null && !val.getQualifiedName().equals(wrapper.getQualifiedName())) { try { wrapper = val; MapProvider provider = MapProvider.Factory.find(val.getQualifiedName()); if (provider != null) { process.setMapProvider(provider); property.setValue(provider.getClass().getName()); } } catch (MapProvider.MapProviderNotFoundException ex) { Exceptions.printStackTrace(ex); } } } } } }