package org.openstreetmap.josm.plugins.osminspector; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import javax.swing.Action; import javax.swing.Icon; import javax.swing.ProgressMonitor; import org.geotools.data.DataUtilities; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.FeatureLayer; import org.geotools.map.MapContent; import org.geotools.referencing.CRS; import org.geotools.renderer.lite.StreamingRenderer; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Fill; import org.geotools.styling.Graphic; import org.geotools.styling.Mark; import org.geotools.styling.Rule; import org.geotools.styling.Stroke; import org.geotools.styling.Style; import org.geotools.styling.StyleFactory; import org.geotools.styling.Symbolizer; import org.opengis.feature.Property; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.FilterFactory2; import org.opengis.filter.identity.FeatureId; import org.opengis.filter.spatial.Intersects; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.plugins.osminspector.gui.OsmInspectorDialog; import org.openstreetmap.josm.tools.ImageProvider; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; @SuppressWarnings({ "deprecation"}) public class OsmInspectorLayer extends Layer { private StyleFactory sf = CommonFactoryFinder.getStyleFactory(null); private FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null); private StreamingRenderer renderer; private CoordinateReferenceSystem crsOSMI; private GeomType geometryType; private String geometryAttributeName; private SimpleFeatureSource featureSource; private MapContent content; private boolean bIsChanged; private int layerOffset = 1; private ArrayList<GeomType> selectGeomType; private Color[] featureFills = { new Color(255, 0, 0), new Color(0, 0, 255), // duplicate ways new Color(204, 204, 0), // minor 5 new Color(255, 230, 128), // minor 2 new Color(255, 204, 0), // minor 1 new Color(255, 102, 102), // major 5 new Color(255, 148, 77), // major 2 new Color(255, 0, 0), // major 1 new Color(255, 255, 64), // selected new Color(255, 0, 0), new Color(255, 0, 0) }; /** * dialog showing the bug info */ private OsmInspectorDialog dialog; /** * supported actions */ // Container for bugs from Osmi private ArrayList<OSMIFeatureTracker> arrFeatures; private LinkedHashMap<BugInfo, Long> osmiBugInfo; public Geometry getOsmBugGeometry(int index) { BugInfo[] array = new BugInfo[osmiBugInfo.keySet().size()]; array = osmiBugInfo.keySet().toArray(array); return array[index].getGeom(); } public Map<BugInfo, Long> getOsmiBugInfo() { return osmiBugInfo; } public SimpleFeatureSource getFeatureSource() { return featureSource; } public void setFeatureSource(SimpleFeatureSource featureSource) { this.featureSource = featureSource; } public boolean isbIsChanged() { return bIsChanged; } public void setbIsChanged(boolean bIsChanged) { this.bIsChanged = bIsChanged; } public ArrayList<OSMIFeatureTracker> getArrFeatures() { return arrFeatures; } public void setArrFeatures(ArrayList<OSMIFeatureTracker> arrFeatures) { this.arrFeatures = arrFeatures; } public BugIndex getOsmiIndex() { return osmiIndex; } // Pointer to prev and next osmi bugs private BugIndex osmiIndex; /** * * The Bug attribute class: hold geom, id and description for that bug * * @author snikhil * */ public class BugInfo implements Comparable<BugInfo>{ public Geometry getGeom() { return geom; } public String getDesc() { return desc; } public String getId() { return id; } @Override public int hashCode() { String fid = attributes.get("FID"); String hash = (fid == null || fid.isEmpty()) ? attributes.get("problem_id") : fid; return hash.hashCode(); } @Override public boolean equals(Object obj) { String fid = attributes.get("FID"); String hash = (fid == null || fid.isEmpty()) ? attributes.get("problem_id") : fid; if (obj instanceof BugInfo) { BugInfo b = (BugInfo) obj; String bfid = b.attributes.get("FID"); String bhash = (bfid == null || bfid.isEmpty()) ? b.attributes.get("problem_id") : bfid; return hash.equals(bhash); } return false; } // private final long bugId; //incremental bugId private final long bugId; private final Geometry geom; private final String desc; private final String id; private final Name name; private final Map<String, String> attributes; public BugInfo(SimpleFeature next, long idx) throws IndexOutOfBoundsException, ParseException { bugId = idx; attributes = new HashMap<>(); Collection<Property> properties = next.getProperties(); Iterator<Property> it = properties.iterator(); while (it.hasNext()) { Property p = it.next(); attributes.put(p.getName().toString(), p.getValue().toString()); } this.geom = (Geometry) next.getAttribute(0); this.desc = (String) next.getAttribute("error_desc"); this.id = next.getID(); name = next.getName(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("BugId_").append(String.valueOf(bugId)).append("\n"); return sb.toString(); } public String getContentString() { StringBuilder sb = new StringBuilder(); sb.append("Layer:").append(name.getLocalPart()).append("\n"); Iterator<Entry<String, String>> it = attributes.entrySet() .iterator(); while (it.hasNext()) { Entry<String, String> next = it.next(); sb.append(next.getKey()).append(":").append(next.getValue()) .append("\n"); } return sb.toString(); } public long getBugId() { return bugId; } @Override public int compareTo(BugInfo o) { String fid = attributes.get("FID"); String hash = (fid == null || fid.isEmpty()) ? attributes.get("problem_id") : fid; String ofid = o.attributes.get("FID"); String ohash = (ofid == null || ofid.isEmpty()) ? o.attributes.get("problem_id") : ofid; return hash.compareTo(ohash); } } /** * Helper class that stores the bug next and prev pointers and can navigate * the entire bug list * * @author snikhil * */ public class BugIndex { private int nextIndex; private int previousIndex; private ArrayList<BugInfo> osmBugs; public BugIndex(Map<BugInfo, Long> bugs) { osmBugs = new ArrayList<>(bugs.keySet()); nextIndex = 0; previousIndex = -1; } public BugIndex(Map<BugInfo, Long> bugs, int n, int p) { osmBugs = new ArrayList<>(bugs.keySet()); nextIndex = n; previousIndex = p; } public void next() { previousIndex = nextIndex; nextIndex = ++nextIndex % osmBugs.size(); } public void prev() { nextIndex = previousIndex; previousIndex = previousIndex - 1 < 0 ? osmBugs.size() - 1 : --previousIndex; } public BugInfo getItemPointedByNext() { return osmBugs.get(nextIndex); } public BugInfo getItemPointedByPrev() { return osmBugs.get(nextIndex); } public int indexOf(BugInfo b) { return osmBugs.indexOf(b); } public BugInfo getNext() { next(); return osmBugs.get(nextIndex); } public BugInfo getPrev() { prev(); return osmBugs.get(nextIndex); } public ArrayList<BugInfo> getBugs() { return osmBugs; } public void append(LinkedHashMap<BugInfo, Long> osmiBugInfo) { Iterator<BugInfo> it = osmiBugInfo.keySet().iterator(); while(it.hasNext()){ BugInfo next = it.next(); if(!osmBugs.contains(next)){ this.osmBugs.add(next); } } } } private enum GeomType { POINT, LINE, POLYGON } private static final Color SELECTED_COLOUR = Color.ORANGE; private static final float SELECTED_POINT_SIZE = 15.0f; private static final float OPACITY = 1.0f; private static final float LINE_WIDTH = 1.0f; private static final float POINT_SIZE = 10.0f; /** * * @param wfsClient * @throws NoSuchAuthorityCodeException * @throws FactoryException * @throws IOException * @throws IndexOutOfBoundsException * @throws ParseException */ public OsmInspectorLayer(GeoFabrikWFSClient wfsClient, ProgressMonitor monitor) throws NoSuchAuthorityCodeException, FactoryException, IOException, IndexOutOfBoundsException, ParseException { super("OsmInspector"); arrFeatures = new ArrayList<>(); osmiBugInfo = new LinkedHashMap<>(); selectGeomType = new ArrayList<>(); // Step 3 - discovery; enhance to iterate over all types with bounds String typeNames[] = wfsClient.getTypeNames(); renderer = new StreamingRenderer(); CRS.decode(Main.getProjection().toCode()); crsOSMI = CRS.decode("EPSG:4326"); content = new MapContent(crsOSMI); selectGeomType.add(GeomType.POINT); for (int idx = 1; idx < typeNames.length; ++idx) { String typeName = typeNames[idx]; Set<FeatureId> selectedFeatures = new HashSet<>(); FeatureCollection<SimpleFeatureType, SimpleFeature> features = wfsClient .getFeatures(typeName, monitor); setGeometry(selectGeomType, typeName); Main.info("Osm Inspector Features size: " + features.size()); Style style = createDefaultStyle(idx, selectedFeatures); OSMIFeatureTracker tracker = new OSMIFeatureTracker(features); arrFeatures.add(tracker); FeatureIterator<SimpleFeature> it = tracker.getFeatures().features(); while (it.hasNext()) { BugInfo theInfo = new BugInfo(it.next(), osmiBugInfo.size()); osmiBugInfo.put(theInfo, theInfo.bugId); } content.addLayer(new FeatureLayer(tracker.getFeatures(), style)); } osmiIndex = new BugIndex(osmiBugInfo); content.setTitle("Osm Inspector Errors"); renderer.setMapContent(content); bIsChanged = true; // finally initialize the dialog dialog = new OsmInspectorDialog(this); this.updateView(); } /** * * @param wfsClient * @throws NoSuchAuthorityCodeException * @throws FactoryException * @throws IOException * @throws ParseException * @throws NoSuchElementException * @throws IndexOutOfBoundsException */ public void loadFeatures(GeoFabrikWFSClient wfsClient) throws NoSuchAuthorityCodeException, FactoryException, IOException, IndexOutOfBoundsException, NoSuchElementException, ParseException { String typeNames[] = wfsClient.getTypeNames(); content.layers().clear(); selectGeomType.clear(); selectGeomType.add(GeomType.POINT); ProgressMonitor monitor = new ProgressMonitor(Main.map.mapView, "Loading features", "", 0, 100); for (int idx = 1; idx < typeNames.length; ++idx) { String typeName = typeNames[idx]; Set<FeatureId> selectedFeatures = new HashSet<>(); monitor.setProgress(100 / typeNames.length * idx); FeatureCollection<SimpleFeatureType, SimpleFeature> features = wfsClient .getFeatures(typeName, monitor); setGeometry(selectGeomType, typeName); Main.info("Osm Inspector Features size: " + features.size()); OSMIFeatureTracker tracker = arrFeatures.get(idx - layerOffset); tracker.mergeFeatures(features); FeatureIterator<SimpleFeature> it = tracker.getFeatures().features(); while (it.hasNext()) { BugInfo theInfo = new BugInfo(it.next(), osmiBugInfo.size()); if (!osmiBugInfo.keySet().contains(theInfo)) { osmiBugInfo.put(theInfo, theInfo.bugId); } } Style style = createDefaultStyle(idx, selectedFeatures); content.addLayer(new FeatureLayer(tracker.getFeatures(), style)); } osmiIndex.append(osmiBugInfo); monitor.setProgress(100); monitor.close(); bIsChanged = true; //dialog.updateDialog(this); dialog.refreshModel(); //dialog.updateNextPrevAction(this); this.updateView(); } private Style createDefaultStyle(int idx, Set<FeatureId> IDs) { Color fillColor = featureFills[idx]; Rule selectedRule = createRule(SELECTED_COLOUR, SELECTED_COLOUR, true); selectedRule.setFilter(ff.id(IDs)); Rule rule = createRule(fillColor, fillColor, false); rule.setElseFilter(true); FeatureTypeStyle fts = sf.createFeatureTypeStyle(); fts.rules().add(selectedRule); fts.rules().add(rule); Style style = sf.createStyle(); style.featureTypeStyles().add(fts); return style; } private Rule createRule(Color outlineColor, Color fillColor, boolean bSelected) { Symbolizer symbolizer = null; Fill fill = null; Stroke stroke = sf.createStroke(ff.literal(outlineColor), ff.literal(LINE_WIDTH)); switch (geometryType) { case POLYGON: fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY)); symbolizer = sf.createPolygonSymbolizer(stroke, fill, geometryAttributeName); break; case LINE: symbolizer = sf.createLineSymbolizer(stroke, geometryAttributeName); break; case POINT: fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY)); Mark mark = sf.getTriangleMark(); mark.setFill(fill); mark.setStroke(stroke); Graphic graphic = sf.createDefaultGraphic(); graphic.graphicalSymbols().clear(); graphic.graphicalSymbols().add(mark); graphic.setSize(ff.literal(bSelected ? SELECTED_POINT_SIZE : POINT_SIZE)); symbolizer = sf.createPointSymbolizer(graphic, geometryAttributeName); } Rule rule = sf.createRule(); rule.symbolizers().add(symbolizer); return rule; } private void setGeometry(ArrayList<GeomType> selectedTypes, String typename) { Main.info("Passed type is" + typename); if (typename.compareTo("duplicate_ways") == 0) { geometryType = GeomType.LINE; } else geometryType = GeomType.POINT; selectedTypes.add(geometryType); } @Override public Icon getIcon() { return ImageProvider.get("layer/osmdata_small"); } @Override public Object getInfoComponent() { return getToolTipText(); } @Override public Action[] getMenuEntries() { return new Action[] {}; } @Override public String getToolTipText() { return org.openstreetmap.josm.tools.I18n.tr("OsmInspector"); } @Override public boolean isMergable(Layer other) { return false; } @Override public void mergeFrom(Layer from) { return; } @Override public void paint(Graphics2D g, MapView mv, Bounds box) { LatLon min = box.getMin(); LatLon max = box.getMax(); Envelope envelope2 = new Envelope(Math.min(min.lat(), max.lat()), Math.max(min.lat(), max.lat()), Math.min(min.lon(), max.lon()), Math.max(min.lon(), max.lon())); ReferencedEnvelope mapArea = new ReferencedEnvelope(envelope2, crsOSMI); renderer.setInteractive(false); renderer.paint(g, mv.getBounds(), mapArea); bIsChanged = false; } @Override public void visitBoundingBox(BoundingXYVisitor v) { } public boolean isChanged() { return bIsChanged; } public void updateView() { this.dialog.revalidate(); Main.map.mapView.revalidate(); Main.map.repaint(); } public void selectFeatures(int x, int y) { int pixelDelta = 2; LatLon clickUL = Main.map.mapView.getLatLon(x - pixelDelta, y - pixelDelta); LatLon clickLR = Main.map.mapView.getLatLon(x + pixelDelta, y + pixelDelta); Envelope envelope = new Envelope( Math.min(clickUL.lon(), clickLR.lon()), Math.max(clickUL.lon(), clickLR.lon()), Math.min(clickUL.lat(), clickLR.lat()), Math.max(clickUL.lat(), clickLR.lat())); ReferencedEnvelope mapArea = new ReferencedEnvelope(envelope, crsOSMI); Intersects filter = ff.intersects(ff.property("msGeometry"), ff.literal(mapArea)); // // Select features in all layers // content.layers().clear(); // Iterate through features and build a list that intersects the above // envelope for (int idx = 0; idx < arrFeatures.size(); ++idx) { OSMIFeatureTracker tracker = arrFeatures.get(idx); FeatureCollection<SimpleFeatureType, SimpleFeature> features = tracker .getFeatures(); SimpleFeatureSource tempfs = DataUtilities.source(features); FeatureCollection<SimpleFeatureType, SimpleFeature> selectedFeatures; try { selectedFeatures = tempfs.getFeatures(filter); Set<FeatureId> IDs = new HashSet<>(); try (FeatureIterator<SimpleFeature> iter = selectedFeatures.features()) { Main.info("Selected features " + selectedFeatures.size()); while (iter.hasNext()) { SimpleFeature feature = iter.next(); IDs.add(feature.getIdentifier()); } } geometryType = selectGeomType.get(idx + layerOffset); Style style = createDefaultStyle(idx + layerOffset, IDs); content.addLayer(new FeatureLayer(features, style)); } catch (IOException e) { Main.error(e); } } bIsChanged = true; } public void selectFeatures(LatLon center) { Point point = Main.map.mapView.getPoint(center); selectFeatures(point.x, point.y); } public void setOsmiIndex(int firstIndex) { osmiIndex.nextIndex = firstIndex; osmiIndex.previousIndex = firstIndex - 1 >= 0 ? firstIndex - 1 : osmiBugInfo.size() - 1; } }