// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.measurement; /// @author Raphael Mack <ramack@raphael-mack.de> import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Box; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.gpx.GpxTrack; import org.openstreetmap.josm.data.gpx.GpxTrackSegment; import org.openstreetmap.josm.data.gpx.WayPoint; import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.NavigatableComponent; import org.openstreetmap.josm.gui.dialogs.LayerListDialog; import org.openstreetmap.josm.gui.dialogs.LayerListPopup; import org.openstreetmap.josm.gui.layer.GpxLayer; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.tools.ImageProvider; /** * This is a layer that draws a grid */ public class MeasurementLayer extends Layer { public MeasurementLayer(String name) { super(name); } private static Icon icon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(MeasurementPlugin.class.getResource("/images/measurement.png"))); private Collection<WayPoint> points = new ArrayList<>(32); @Override public Icon getIcon() { return icon; } @Override public String getToolTipText() { return tr("Layer to make measurements"); } @Override public boolean isMergable(Layer other) { //return other instanceof MeasurementLayer; return false; } @Override public void mergeFrom(Layer from) { // TODO: nyi - doubts about how this should be done are around. Ideas? } @Override public void paint(Graphics2D g, final MapView mv, Bounds bounds) { g.setColor(Color.green); Point l = null; for(WayPoint p:points){ Point pnt = mv.getPoint(p.getCoor()); if (l != null){ g.drawLine(l.x, l.y, pnt.x, pnt.y); } g.drawOval(pnt.x - 2, pnt.y - 2, 4, 4); l = pnt; } } @Override public void visitBoundingBox(BoundingXYVisitor v) { // nothing to do here } @Override public Object getInfoComponent() { return getToolTipText(); } @Override public Action[] getMenuEntries() { return new Action[]{ LayerListDialog.getInstance().createShowHideLayerAction(), // TODO: implement new JMenuItem(new LayerListDialog.DeleteLayerAction(this)), SeparatorLayerAction.INSTANCE, new GPXLayerImportAction(this), SeparatorLayerAction.INSTANCE, new LayerListPopup.InfoAction(this)}; } public void removeLastPoint(){ WayPoint l = null; for(WayPoint p:points) l = p; if(l != null) points.remove(l); recalculate(); Main.map.repaint(); } public void mouseClicked(MouseEvent e){ if (e.getButton() != MouseEvent.BUTTON1) return; LatLon coor = Main.map.mapView.getLatLon(e.getX(), e.getY()); points.add(new WayPoint(coor)); Main.map.repaint(); recalculate(); } public void reset(){ points.clear(); recalculate(); Main.map.repaint(); } private void recalculate(){ double pathLength = 0.0, segLength = 0.0; // in meters WayPoint last = null; pathLength = 0.0; for(WayPoint p : points){ if(last != null){ segLength = calcDistance(last, p); pathLength += segLength; } last = p; } if (MeasurementPlugin.measurementDialog != null) { MeasurementPlugin.measurementDialog.pathLengthLabel.setText(NavigatableComponent.getDistText(pathLength)); } if (Main.map.mapMode instanceof MeasurementMode) { Main.map.statusLine.setDist(pathLength); } } /* * Use an equal area sinusoidal projection to improve accuracy and so we can still use normal polygon area calculation * https://stackoverflow.com/questions/4681737/how-to-calculate-the-area-of-a-polygon-on-the-earths-surface-using-python */ public static double calcX(LatLon p1){ return p1.lat() * Math.PI * 6367000 / 180; } public static double calcY(LatLon p1){ return p1.lon() * ( Math.PI * 6367000 / 180) * Math.cos(p1.lat() * Math.PI / 180); } public static double calcDistance(WayPoint p1, WayPoint p2){ return p1.getCoor().greatCircleDistance(p2.getCoor()); } public static double angleBetween(WayPoint p1, WayPoint p2){ return angleBetween(p1.getCoor(), p2.getCoor()); } public static double angleBetween(LatLon p1, LatLon p2){ double lat1, lon1, lat2, lon2; double dlon; lat1 = p1.lat() * Math.PI / 180.0; lon1 = p1.lon() * Math.PI / 180.0; lat2 = p2.lat() * Math.PI / 180.0; lon2 = p2.lon() * Math.PI / 180.0; dlon = lon2 - lon1; double coslat2 = Math.cos(lat2); return (180 * Math.atan2(coslat2 * Math.sin(dlon), (Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * coslat2 * Math.cos(dlon)))) / Math.PI; } public static double oldAngleBetween(LatLon p1, LatLon p2){ double lat1, lon1, lat2, lon2; double dlon, dlat; double heading; lat1 = p1.lat() * Math.PI / 180.0; lon1 = p1.lon() * Math.PI / 180.0; lat2 = p2.lat() * Math.PI / 180.0; lon2 = p2.lon() * Math.PI / 180.0; dlon = lon2 - lon1; dlat = lat2 - lat1; double a = (Math.pow(Math.sin(dlat/2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon/2), 2)); double d = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); heading = Math.acos((Math.sin(lat2) - Math.sin(lat1) * Math.cos(d)) / (Math.sin(d) * Math.cos(lat1))); if (Math.sin(lon2 - lon1) < 0) { heading = 2 * Math.PI - heading; } return heading * 180 / Math.PI; } private class GPXLayerImportAction extends AbstractAction { /** * The data model for the list component. */ private DefaultListModel<GpxLayer> model = new DefaultListModel<>(); /** * @param layer the targeting measurement layer */ public GPXLayerImportAction(MeasurementLayer layer) { super(tr("Import path from GPX layer"), ImageProvider.get("dialogs", "edit")); // TODO: find better image } @Override public void actionPerformed(ActionEvent e) { Box panel = Box.createVerticalBox(); final JList<GpxLayer> layerList = new JList<>(model); Collection<Layer> data = Main.getLayerManager().getLayers(); Layer lastLayer = null; int layerCnt = 0; for (Layer l : data) { if (l instanceof GpxLayer) { model.addElement((GpxLayer) l); lastLayer = l; layerCnt++; } } if (layerCnt == 1) { layerList.setSelectedValue(lastLayer, true); } if (layerCnt > 0) { layerList.setCellRenderer(new DefaultListCellRenderer(){ @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Layer layer = (Layer)value; JLabel label = (JLabel)super.getListCellRendererComponent(list, layer.getName(), index, isSelected, cellHasFocus); Icon icon = layer.getIcon(); label.setIcon(icon); label.setToolTipText(layer.getToolTipText()); return label; } }); JCheckBox dropFirst = new JCheckBox(tr("Drop existing path")); panel.add(layerList); panel.add(dropFirst); final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION){ @Override public void selectInitialValue() { layerList.requestFocusInWindow(); } }; final JDialog dlg = optionPane.createDialog(Main.parent, tr("Import path from GPX layer")); dlg.setVisible(true); Object answer = optionPane.getValue(); if (answer == null || answer == JOptionPane.UNINITIALIZED_VALUE || (answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION)) { return; } GpxLayer gpx = layerList.getSelectedValue(); if (dropFirst.isSelected()) { points = new ArrayList<>(32); } for (GpxTrack trk : gpx.data.tracks) { for (GpxTrackSegment trkseg : trk.getSegments()) { for(WayPoint p: trkseg.getWayPoints()){ points.add(p); } } } recalculate(); Main.parent.repaint(); } else{ // TODO: register a listener and show menu entry only if gps layers are available // no gps layer JOptionPane.showMessageDialog(Main.parent,tr("No GPX data layer found.")); } } } }