// License: GPL. For details, see LICENSE file. package relcontext; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Stroke; import java.awt.geom.GeneralPath; import java.util.HashSet; import java.util.Set; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; import org.openstreetmap.josm.data.osm.event.DataChangedEvent; import org.openstreetmap.josm.data.osm.event.DataSetListener; import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; import org.openstreetmap.josm.gui.layer.MapViewPaintable; import org.openstreetmap.josm.gui.layer.OsmDataLayer; /** * Chosen relation; is used for all actions and is highlighted on the map. * * @author Zverik */ public class ChosenRelation implements ActiveLayerChangeListener, MapViewPaintable, DataSetListener { protected Relation chosenRelation = null; private Set<ChosenRelationListener> chosenRelationListeners = new HashSet<>(); public void set(Relation rel) { if (rel == chosenRelation || (rel != null && chosenRelation != null && rel.equals(chosenRelation))) return; // new is the same as old Relation oldRel = chosenRelation; chosenRelation = rel; analyse(); Main.map.mapView.repaint(); fireRelationChanged(oldRel); } protected void fireRelationChanged(Relation oldRel) { for (ChosenRelationListener listener : chosenRelationListeners) { listener.chosenRelationChanged(oldRel, chosenRelation); } } public Relation get() { return chosenRelation; } public void clear() { set(null); } public boolean isSame(Object r) { if (r == null) return chosenRelation == null; else if (!(r instanceof Relation)) return false; else return chosenRelation != null && r.equals(chosenRelation); } private static final String[] MULTIPOLYGON_TYPES = new String[] { "multipolygon", "boundary" }; /** * Check if the relation type assumes all ways inside it form a multipolygon. */ public boolean isMultipolygon() { return isMultipolygon(chosenRelation); } public static boolean isMultipolygon(Relation r) { if (r == null) return false; String type = r.get("type"); if (type == null) return false; for (String t : MULTIPOLYGON_TYPES) { if (t.equals(type)) return true; } return false; } public int getSegmentsCount() { return 0; } public int getCirclesCount() { return 0; } protected void analyse() { // todo } public void addChosenRelationListener(ChosenRelationListener listener) { chosenRelationListeners.add(listener); } public void removeChosenRelationListener(ChosenRelationListener listener) { chosenRelationListeners.remove(listener); } @Override public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { // todo: dim chosen relation when changing layer // todo: check this WTF! OsmDataLayer newLayer = Main.getLayerManager().getEditLayer(); clear(); if (newLayer != null && e.getPreviousEditLayer() == null) { Main.map.mapView.addTemporaryLayer(this); } else if (newLayer == null) { Main.map.mapView.removeTemporaryLayer(this); } } @Override public void paint(Graphics2D g, MapView mv, Bounds bbox) { if (chosenRelation == null) return; OsmDataLayer dataLayer = mv.getLayerManager().getEditLayer(); float opacity = dataLayer == null ? 0.0f : !dataLayer.isVisible() ? 0.0f : (float) dataLayer.getOpacity(); if (opacity < 0.01) return; Composite oldComposite = g.getComposite(); Stroke oldStroke = g.getStroke(); g.setStroke(new BasicStroke(9, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.setColor(Color.yellow); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f * opacity)); drawRelations(g, mv, bbox, chosenRelation, new HashSet<Relation>()); g.setComposite(oldComposite); g.setStroke(oldStroke); } private void drawRelations(Graphics2D g, MapView mv, Bounds bbox, Relation rel, Set<Relation> visitedRelations) { if (!visitedRelations.contains(rel)) { visitedRelations.add(rel); for (OsmPrimitive element : rel.getMemberPrimitives()) { if (null != element.getType()) switch(element.getType()) { case NODE: Node node = (Node) element; Point center = mv.getPoint(node); g.drawOval(center.x - 4, center.y - 4, 9, 9); break; case WAY: Way way = (Way) element; if (way.getNodesCount() >= 2) { GeneralPath b = new GeneralPath(); Point p = mv.getPoint(way.getNode(0)); b.moveTo(p.x, p.y); for (int i = 1; i < way.getNodesCount(); i++) { p = mv.getPoint(way.getNode(i)); b.lineTo(p.x, p.y); } g.draw(b); } break; case RELATION: Color oldColor = g.getColor(); g.setColor(Color.magenta); drawRelations(g, mv, bbox, (Relation) element, visitedRelations); g.setColor(oldColor); break; default: break; } } } } @Override public void relationMembersChanged(RelationMembersChangedEvent event) { if (chosenRelation != null && event.getRelation().equals(chosenRelation)) { fireRelationChanged(chosenRelation); } } @Override public void tagsChanged(TagsChangedEvent event) { if (chosenRelation != null && event.getPrimitive().equals(chosenRelation)) { fireRelationChanged(chosenRelation); } } @Override public void dataChanged(DataChangedEvent event) { if (chosenRelation != null) { fireRelationChanged(chosenRelation); } } @Override public void primitivesRemoved(PrimitivesRemovedEvent event) { if (chosenRelation != null && event.getPrimitives().contains(chosenRelation)) { clear(); } } @Override public void wayNodesChanged(WayNodesChangedEvent event) { if (chosenRelation != null) { fireRelationChanged(chosenRelation); // download incomplete primitives doesn't cause dataChanged event } } @Override public void primitivesAdded(PrimitivesAddedEvent event) {} @Override public void nodeMoved(NodeMovedEvent event) {} @Override public void otherDatasetChange(AbstractDatasetChangedEvent event) {} }