Ticket #24741: 24741.patch

File 24741.patch, 8.9 KB (added by GerdP, 5 days ago)

imrpove handling of closed roundabouts at the beginning and/or end of a track segment

  • src/org/openstreetmap/josm/actions/relation/ExportRelationToGpxAction.java

     
    2424
    2525import org.openstreetmap.josm.actions.GpxExportAction;
    2626import org.openstreetmap.josm.actions.IPrimitiveAction;
     27import org.openstreetmap.josm.data.coor.LatLon;
    2728import org.openstreetmap.josm.data.gpx.GpxData;
    2829import org.openstreetmap.josm.data.gpx.GpxTrack;
    2930import org.openstreetmap.josm.data.gpx.WayPoint;
     
    3132import org.openstreetmap.josm.data.osm.Node;
    3233import org.openstreetmap.josm.data.osm.Relation;
    3334import org.openstreetmap.josm.data.osm.RelationMember;
     35import org.openstreetmap.josm.data.osm.Way;
    3436import org.openstreetmap.josm.gui.MainApplication;
    3537import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
    3638import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
     
    151153
    152154            List<WayConnectionType> wct = new WayConnectionTypeCalculator().updateLinks(flat);
    153155            final HashMap<String, Integer> names = new HashMap<>();
     156            List<Way> roundabouts = new ArrayList<>();
    154157            for (int i = 0; i < flat.size(); i++) {
    155158                WayConnectionType wayConnectionType = wct.get(i);
    156                 if (!wayConnectionType.isOnewayLoopBackwardPart && !wayConnectionType.direction.isRoundabout()) {
    157                     if (!wayConnectionType.linkPrev && !trkseg.isEmpty()) {
    158                         gpxData.addTrack(new GpxTrack(trk, trkAttr));
    159                         trkAttr.clear();
    160                         trk.clear();
    161                         trkseg.clear();
    162                         trk.add(trkseg);
     159                if (!wayConnectionType.linkPrev && !trkseg.isEmpty()) {
     160                    checkRoundabouts(trkseg, roundabouts);
     161                    roundabouts.clear();
     162                    time = setTime(trkseg, time);
     163                    gpxData.addTrack(new GpxTrack(trk, trkAttr));
     164                    trkAttr.clear();
     165                    trk.clear();
     166                    trkseg.clear();
     167                    trk.add(trkseg);
     168                }
     169                if (trkAttr.isEmpty()) {
     170                    flat.get(i).getWay().referrers(Relation.class)
     171                            .filter(relsFound::contains)
     172                            .findFirst()
     173                            .ifPresent(r -> {
     174                                trkAttr.put("name", r.getName() != null ? r.getName() : Long.toString(r.getId()));
     175                                trkAttr.put("desc", tr("based on osm route relation data, timestamps are synthetic"));
     176                            });
     177                    GpxData.ensureUniqueName(trkAttr, names, (String) trkAttr.get("name"));
     178                }
     179                if (wayConnectionType.isOnewayLoopBackwardPart)
     180                    continue;
     181                if (wayConnectionType.direction.isRoundabout()) {
     182                    roundabouts.add(flat.get(i).getWay());
     183                    continue;
     184                }
     185                List<Node> ln = flat.get(i).getWay().getNodes();
     186                if (wayConnectionType.direction == WayConnectionType.Direction.BACKWARD)
     187                    Collections.reverse(ln);
     188                for (Node n: ln) {
     189                    WayPoint point = OsmDataLayer.nodeToWayPoint(n, Long.MIN_VALUE);
     190                    if (!trkseg.isEmpty()) {
     191                        // see #24745 don't add connecting way nodes twice
     192                        WayPoint last = trkseg.get(trkseg.size() - 1);
     193                        if (point.getCoor().equals(last.getCoor()))
     194                            continue;
    163195                    }
    164                     if (trkAttr.isEmpty()) {
    165                         flat.get(i).getWay().referrers(Relation.class)
    166                                 .filter(relsFound::contains)
    167                                 .findFirst()
    168                                 .ifPresent(r -> {
    169                                     trkAttr.put("name", r.getName() != null ? r.getName() : Long.toString(r.getId()));
    170                                     trkAttr.put("desc", tr("based on osm route relation data, timestamps are synthetic"));
    171                                 });
    172                         GpxData.ensureUniqueName(trkAttr, names, (String) trkAttr.get("name"));
    173                     }
    174                     List<Node> ln = flat.get(i).getWay().getNodes();
    175                     if (wayConnectionType.direction == WayConnectionType.Direction.BACKWARD)
    176                         Collections.reverse(ln);
    177                     for (Node n: ln) {
    178                         WayPoint point = OsmDataLayer.nodeToWayPoint(n, TimeUnit.SECONDS.toMillis(time));
    179                         if (!trkseg.isEmpty()) {
    180                             // see #24745 don't add connecting way nodes twice
    181                             WayPoint last = trkseg.get(trkseg.size() - 1);
    182                             if (point.getCoor().equals(last.getCoor()))
    183                                 continue;
    184                         }
    185                         trkseg.add(point);
    186                         time += 1;
    187                     }
     196                    trkseg.add(point);
    188197                }
    189198            }
    190             gpxData.addTrack(new GpxTrack(trk, trkAttr));
     199            if (!trkseg.isEmpty()) {
     200                checkRoundabouts(trkseg, roundabouts);
     201                time = setTime(trkseg, time);
     202                gpxData.addTrack(new GpxTrack(trk, trkAttr));
     203            }
    191204
    192205            String lprefix = relations.iterator().next().getName();
    193206            if (lprefix == null || relations.size() > 1)
     
    200213        return new GpxLayer(gpxData, layerName, true);
    201214    }
    202215
     216    /**
     217     * Set time for the points in the given track segment and return last used value. For each point the time is incremented by 1.
     218     * @param trkseg the track segment
     219     * @param time the time value to start with
     220     * @return last used time value
     221     */
     222    private long setTime(List<WayPoint> trkseg, long time) {
     223        for (WayPoint p : trkseg) {
     224            p.setTimeInMillis(TimeUnit.SECONDS.toMillis(time));
     225            time++;
     226        }
     227        return time;
     228    }
     229
     230    /**
     231     * Handle closed roundabout at beginning and/or end of track segment
     232     * @param trkseg the track segment
     233     * @param roundabouts list of roundabout ways
     234     */
     235    private void checkRoundabouts(List<WayPoint> trkseg, List<Way> roundabouts) {
     236        for (Way roundabout : roundabouts) {
     237            checkRoundabout(trkseg, roundabout);
     238        }
     239    }
     240
     241    /**
     242     * Check if roundabout is at the beginning and/or end of track segment and possibly add further nodes.
     243     * See #24741
     244     * @param trkseg the track segment
     245     * @param roundabout the (closed) roundabout
     246     */
     247    private void checkRoundabout(List<WayPoint> trkseg, Way roundabout) {
     248        if (trkseg.isEmpty())
     249            return;
     250        LatLon first = trkseg.get(0).getCoor();
     251        LatLon last = trkseg.get(trkseg.size() - 1).getCoor();
     252        boolean hasFirst = roundabout.getNodes().stream().anyMatch(p -> p.getCoor().equals(first));
     253        boolean hasLast = roundabout.getNodes().stream().anyMatch(p -> p.getCoor().equals(last));
     254        if (hasFirst && hasLast) {
     255            // track starts and ends with the same closed roundabout and thus is a round trip, we want to add part of the circle or
     256            // just the connecting line between first and last to close the gap
     257            if (mode.contains(FROM_FIRST_MEMBER)) {
     258                trkseg.add(0, new WayPoint(trkseg.get(trkseg.size() - 1)));
     259            } else {
     260                trkseg.add(new WayPoint(trkseg.get(0)));
     261            }
     262        } else if (hasFirst || hasLast) {
     263            // a closed roundabout is before or after a gap, we want to add the full circle but we have to rotate first
     264            int pos = -1;
     265            List<Node> ln = new ArrayList<>(roundabout.getNodes());
     266            ln.remove(0);
     267            for (int i = 0; i < ln.size(); i++) {
     268                LatLon current = roundabout.getNode(i).getCoor();
     269                if (current.equals(first) || current.equals(last)) {
     270                    pos = i;
     271                    break;
     272                }
     273            }
     274            Collections.rotate(ln, -pos);
     275            ln.add(ln.get(0));
     276            for (Node n: ln) {
     277                WayPoint point = OsmDataLayer.nodeToWayPoint(n, Long.MIN_VALUE);
     278                if (hasFirst)
     279                    trkseg.add(0, point);
     280                else
     281                    trkseg.add(point);
     282            }
     283        }
     284    }
     285
    203286    private <T> Iterator<T> modeAwareIterator(List<T> list) {
    204287        return mode.contains(FROM_FIRST_MEMBER)
    205288                ? list.iterator()