Subversion Repositories mkgmap

Rev

Rev 3408 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2013.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */


package uk.me.parabola.mkgmap.reader.osm;

import java.util.ArrayList;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.MapCollector;


/**
 * Representation of an OSM turn restriction
 *
 * @author Mark Burton, GerdP
 */

public class RestrictionRelation extends Relation {
    private static final Logger log = Logger.getLogger(RestrictionRelation.class);

    private List<Long> fromWayIds = new ArrayList<>(2);
    private List<Long> toWayIds = new ArrayList<>(2);
    private List<Long> viaWayIds = new ArrayList<>(2);
    private List<Coord> viaPoints = new ArrayList<>(2);
    private Coord viaCoord;
    private String restriction;
        private byte exceptMask;
        private char dirIndicator;
    private String messagePrefix;
    private boolean valid;
    private boolean evalWasCalled;
   
        // These tags are not loaded by default but if they exist issue a warning
        private final static String[] unsupportedTags = { "day_on", "day_off", "hour_on", "hour_off" };
       
        private final static byte DEFAULT_EXCEPT_MASK = AccessTagsAndBits.FOOT  | AccessTagsAndBits.EMERGENCY;
       
        private final static List<String> supportedRestrictions = Arrays.asList(
                "no_right_turn", "no_left_turn", "no_u_turn", "no_straight_on",
                "only_right_turn", "only_left_turn", "only_straight_on",
                "no_entry", "no_exit"        
        );
       
        /**
         * Create an instance based on an existing relation.  We need to do
         * this because the type of the relation is not known until after all
         * its tags are read in.
         * @param other The relation to base this one on.
         */

        public RestrictionRelation(Relation other) {
                setId(other.getId());
                messagePrefix = "Turn restriction " + toBrowseURL();
                copyTags(other);
                for (Map.Entry<String, Element> pair : other.getElements()) {
                        addElement(pair.getKey(), pair.getValue());
                }
        }
       
        /**
         * The evaluation should happen after style processing.
         * Normally this is called from the {@link RelationStyleHook}
         * Performs also diverse plausibility checks.
         * @param bbox tile boundary
         */

        public void eval(Area bbox){
                if (evalWasCalled){
                        log.error(messagePrefix,"internal error: eval() was already called");
                        fromWayIds.clear();
                        toWayIds.clear();
                        viaWayIds.clear();
                }
                evalWasCalled = true;
                if (getTag("type") == null){
                        // style removed the tag
                        log.info(messagePrefix, "type tag was removed, relation is ignored");                          
                        valid = false;
                        return;
                }
               
            List<Way> fromWays = new ArrayList<>();
            List<Way> toWays = new ArrayList<>();
            List<Way> viaWays = new ArrayList<>();
                final String browseURL = toBrowseURL();
                valid = true;
                // find out what kind of restriction we have and to which vehicles it applies
                exceptMask = DEFAULT_EXCEPT_MASK;
                String specifc_type = getTag("restriction");
                int count_unknown = 0;
                Map<String, String> vehicles = getTagsWithPrefix("restriction:", true);
                if (vehicles.isEmpty() == false){
                        exceptMask = (byte) 0xff;
                        Iterator<Entry<String, String>> iter = vehicles.entrySet().iterator();
                        while (iter.hasNext()){
                                Map.Entry<String, String> entry = iter.next();
                                String vehicle = entry.getKey();
                                if (setExceptMask(vehicle, false) == false)
                                        count_unknown++;
                                if (specifc_type == null)
                                        specifc_type = entry.getValue();
                                else if (specifc_type.equals(entry.getValue()) == false){
                                        log.warn(messagePrefix, "is invalid, it specifies different kinds of turns");
                                        valid = false;
                                        break;
                                }
                        }
                        if (valid && vehicles.size() == count_unknown){
                                log.warn(messagePrefix, "no supported vehicle in turn restriction");                           
                                valid = false;
                                return;
                        }
                }
                if (specifc_type == null){
                        // style removed the tag
                        log.info(messagePrefix, "no valid restriction tag found");                             
                        valid = false;
                        return;
                }
                restriction = specifc_type.trim();
               
                messagePrefix = "Turn restriction (" + restriction + ") " + browseURL;
                if (supportedRestrictions.contains(restriction) == false){
                        log.warn(messagePrefix, "ignoring unsupported restriction type '" + restriction + "'");
                        valid = false;
                        return;
                }
               
                String dirInfo = "";
                if (restriction.contains("left"))
                        dirInfo += "l";
                if (restriction.contains("right"))
                        dirInfo += "r";
                if (restriction.contains("straight"))
                        dirInfo += "s";
                if (restriction.endsWith("u_turn"))
                        dirInfo += "u";
                if (dirInfo.length() > 1){
                        log.warn(messagePrefix, "ignoring unsupported restriction type '" + restriction + "'");
                        valid = false;
                        return;
                } else if (dirInfo.length() == 1){
                        dirIndicator = dirInfo.charAt(0);
                } else
                        dirIndicator = '?';
               
                String type = getTag("type");
                if (type.startsWith("restriction:")){
                        exceptMask = (byte) 0xff;
                        String vehicle = type.substring("restriction:".length());
                        if (setExceptMask(vehicle, false) == false) {
                                log.warn(messagePrefix, "ignoring unsupported '" + vehicle + "' in turn restriction");
                                valid = false;
                                return;
                        }
                }
               
                String except = getTag("except");
                if(except != null) {
                        for(String vehicle : except.split("[,;]")) { // be nice
                                vehicle = vehicle.trim();
                                setExceptMask(vehicle, true);
                        }
                }
               
                for (String unsupportedTag : unsupportedTags) {
                        if (getTag(unsupportedTag) != null) {
                                log.warn(messagePrefix, "ignoring unsupported '" + unsupportedTag + "' tag");
                        }
                }
               
                // evaluate members
                for (Map.Entry<String, Element> pair : getElements()) {
                        String role = pair.getKey();
                        Element el = pair.getValue();

                        Coord location = null;

                        if(viaCoord != null)
                                location = viaCoord;
                        else if(!fromWays.isEmpty() && !fromWays.get(0).getPoints().isEmpty())
                                location = fromWays.get(0).getPoints().get(0);
                        else if(!toWays.isEmpty() && !toWays.get(0).getPoints().isEmpty())
                                location = toWays.get(0).getPoints().get(0);
                        else if(!viaWays.isEmpty() && !viaWays.get(0).getPoints().isEmpty())
                                location = viaWays.get(0).getPoints().get(0);

                        if(location != null)
                                messagePrefix = "Turn restriction (" + restriction + ") " + browseURL + " (at " + location.toOSMURL() + ")";

                        if("to".equals(role)) {
                                if(!(el instanceof Way)) {
                                        log.warn(messagePrefix, "'to' member", el.toBrowseURL(), "is not a way but it should be");
                                }
                                else if(((Way)el).getPoints().isEmpty()) {
                                        log.warn(messagePrefix, "ignoring empty 'to' way", el.toBrowseURL());
                                }
                                else
                                        toWays.add((Way)el);
                        }
                        else if("from".equals(role)) {
                                if(!(el instanceof Way)) {
                                        log.warn(messagePrefix, "'from' member", el.toBrowseURL(), "is not a way but it should be");
                                }
                                else if(((Way)el).getPoints().isEmpty()) {
                                        log.warn(messagePrefix, "ignoring empty 'from' way", el.toBrowseURL());
                                }
                                else
                                        fromWays.add((Way)el);
                        }
                        else if("via".equals(role)) {
                                if(el instanceof Node) {
                                        if (viaCoord != null){
                                                log.warn(messagePrefix, "has extra 'via' node", el.toBrowseURL());
                                                valid = false;                                         
                                        } else
                                                viaCoord = ((Node)el).getLocation();
                                }
                                else if(el instanceof Way) {
                                        if (viaCoord != null){
                                                log.warn(messagePrefix, "has extra 'via' way", el.toBrowseURL());
                                                valid = false;                                         
                                        } else
                                                viaWays.add((Way)el);
                                }
                                else {
                                        log.warn(messagePrefix, "'via' member", el.toBrowseURL(), "is not a node or way");
                                }
                        }
                        else if("location_hint".equals(role)) {
                                // relax - we don't care about this
                        }
                        else {
                                log.warn(messagePrefix, "unknown member role '" + role + "'");
                        }
                }

               
                if (!valid)
                        return;
               
                if ("no_entry".equals(restriction) == false){
                        if (fromWays.size() > 1){
                                log.warn(messagePrefix, "multiple 'from' members are only accepted for no_entry restrictions");
                                valid = false;
                                return;
                        }
                }
                if ("no_exit".equals(restriction) == false){
                        if (toWays.size() > 1){
                                log.warn(messagePrefix, "multiple 'to' members are only accepted for no_exit restrictions");
                                valid = false;
                                return;
                        }
                }
                if (viaWays.isEmpty() && viaCoord == null && fromWays.size() == 1 && toWays.size() == 1){
                        Way fromWay = fromWays.get(0);
                        Way toWay = toWays.get(0);
                        List<Coord>fromPoints = fromWay.getPoints();
                        List<Coord>toPoints = toWay.getPoints();
                        int countSame = 0;
                        for(Coord fp : fromPoints) {
                                for(Coord tp : toPoints) {
                                        if(fp == tp){
                                                countSame++;
                                                viaCoord = fp;
                                        }
                                }
                        }
                        if (countSame > 1){
                                log.warn(messagePrefix, "lacks 'via' node and way and the 'from' (", fromWay.toBrowseURL(), ") and 'to' (", toWay.toBrowseURL(), ") ways connect in more than one place");
                                valid = false;
                        } else if (viaCoord == null){
                                log.warn(messagePrefix, "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' (" + toWay.toBrowseURL() + ") ways don't connect");
                                valid = false;
                        } else {
                                if (fromPoints.get(0) != viaCoord && fromPoints.get(fromPoints.size()-1) != viaCoord ||
                                                toPoints.get(0) != viaCoord && toPoints.get(toPoints.size()-1) != viaCoord){
                                        log.warn(messagePrefix, "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' (" + toWay.toBrowseURL() + ") ways don't connect at an end point");
                                        valid = false;
                                } else
                                        log.warn(messagePrefix, "lacks 'via' node (guessing it should be at", viaCoord.toOSMURL() + ", why don't you add it to the OSM data?)");                               
                        }
                }

                if(fromWays.isEmpty() ) {
                        log.warn(messagePrefix, "lacks 'from' way");
                        valid = false;
                }

                if(toWays.isEmpty()) {
                        log.warn(messagePrefix, "lacks 'to' way");
                        valid = false;
                }

                if ((fromWays.size() > 1 || toWays.size() > 1) && viaWays.isEmpty() == false){
                        log.warn(messagePrefix, "'via' way(s) are not supported with multiple 'from' or 'to' ways");
                        valid = false;
                }
                if (toWays.size() == 1 && fromWays.size() == 1 && viaWays.isEmpty()){
                        if ("no_u_turn".equals(restriction) && fromWays.get(0).equals(toWays.get(0))){
                                log.warn(messagePrefix,"no_u_turn with equal 'from' and 'to' way and via node is ignored");
                                valid = false;
                        }
                }
                if (!valid)
                        return;
                for (List<Way> ways : Arrays.asList(fromWays,viaWays,toWays)){
                        for (Way way : ways){
                                if (way.getPoints().size() < 2){
                                        log.warn(messagePrefix,"way",way.toBrowseURL(),"has less than 2 points, restriction is ignored");
                                        valid = false;
                                } else {
                                        if (way.getPoints().get(0) == way.getPoints().get(way.getPoints().size()-1)){
                                                if (ways == toWays && dirIndicator != '?')
                                                        continue; // we try to determine the correct part in RoadNetwork
                                                log.warn(messagePrefix, "way", way.toBrowseURL(), "starts and ends at same node, don't know which one to use");
                                                valid = false;
                                        }
                                }
                        }
                }
                if (!valid)
                        return;
                if (viaPoints.isEmpty() == false)
                        viaCoord = viaPoints.get(0);
               
                if(viaCoord == null && viaWays.isEmpty()) {
                        valid = false;
                        return;
                }
               
                viaPoints.clear();
                Coord v1 = viaCoord;
                Coord v2 = viaCoord;
                if (viaWays.isEmpty() == false){
                        v1 = viaWays.get(0).getPoints().get(0);
                        v2 = viaWays.get(0).getPoints().get(viaWays.get(0).getPoints().size()-1);
                }
                // check if all from ways are connected at the given via point or with the given via ways
                for (Way fromWay : fromWays){
                        Coord e1 = fromWay.getPoints().get(0);
                        Coord e2 = fromWay.getPoints().get(fromWay.getPoints().size() - 1);
                        if (e1 == v1 || e2 == v1)
                                viaCoord = v1;
                        else if (e1 == v2 || e2 == v2)
                                viaCoord = v2;
                        else {
                                log.warn(messagePrefix, "'from' way", fromWay.toBrowseURL(), "doesn't start or end at 'via' node or way");
                                valid = false;
                        }
                }
                if (!valid)
                        return;
                viaPoints.add(viaCoord);
                // check if via ways are connected in the given order
                for (int i = 0; i < viaWays.size();i++){
                        Way way = viaWays.get(i);
                        Coord v = viaPoints.get(viaPoints.size()-1);
                        if (way.getPoints().get(0) == v)
                                v2 = way.getPoints().get(way.getPoints().size()-1);
                        else if (way.getPoints().get(way.getPoints().size()-1) == v)
                                v2 = way.getPoints().get(0);
                        else {
                                log.warn(messagePrefix, "'via' way", way.toBrowseURL(), "doesn't start or end at",v.toDegreeString());
                                valid = false;
                        }
                        viaPoints.add(v2);
                }
               
                // check if all via points are inside the bounding box
                int countInside = 0;
                for (Coord via: viaPoints){
                        if(bbox.contains(via))
                                ++countInside;
                }
                if (countInside == 0)
                        valid = false;
                else if (countInside > 0 && countInside < viaPoints.size()){
                        log.warn(messagePrefix,"via way crosses tile boundary. Don't know how to save that, ignoring it");
                        valid = false;
                }
               
                if (!valid)
                        return;
                // check if all to ways are connected to via point or last via way
                Coord lastVia = viaPoints.get(viaPoints.size()-1);
                for (Way toWay : toWays){
                        Coord e1 = toWay.getPoints().get(0);
                        Coord e2 = toWay.getPoints().get(toWay.getPoints().size() - 1);
                        if(e1 != lastVia && e2 != lastVia) {
                                log.warn(messagePrefix, "'to' way", toWay.toBrowseURL(), "doesn't start or end at 'via' node or way");
                                valid = false;
                        }
                }
                if (valid && !viaWays.isEmpty() && restriction.startsWith("only")){
                        log.warn(messagePrefix, "check: 'via' way(s) are used in",restriction,"restriction");
                }
                if (valid){
                        // make sure that via way(s) don't appear in the from or to lists
                        for (Way w: viaWays){
                                if (fromWays.contains(w)){
                                        log.warn(messagePrefix, "'via' way",w.toBrowseURL(),"appears also as 'from' way");
                                        valid = false;
                                }
                                if (toWays.contains(w)){
                                        log.warn(messagePrefix, "'via' way",w.toBrowseURL(),"appears also as 'to' way");
                                        valid = false;
                                }
                        }
                }
                if (valid){
                        for (Way w: fromWays)
                                fromWayIds.add(w.getId());
                        for (Way w: toWays)
                                toWayIds.add(w.getId());
                        for (Way w: viaWays){
                                w.setViaWay(true);
                                viaWayIds.add(w.getId());
                        }
                        for (Coord v: viaPoints)
                                v.setViaNodeOfRestriction(true);
                }
        }

        /**
         * Match the vehicle type in a restriction with the mkgmap type
         * and modify the exceptMask
         * @param vehicle
         * @param b true: restriction should not apply for vehicle, false: restriction should apply  
         * @return true if vehicle has a matching flag in the garmin format
         */

        private boolean setExceptMask(String vehicle, boolean b){
                byte flag = 0;
                if (vehicle == null)
                        return false;
                // inverted
                if(vehicle.equals("vehicle"))
                        flag = (byte) ~(DEFAULT_EXCEPT_MASK);
                else if(vehicle.equals("motor_vehicle"))
                        flag = (byte) ~(AccessTagsAndBits.BIKE | DEFAULT_EXCEPT_MASK);
                // normal
                else if(vehicle.equals("psv"))
                        flag = (byte) (AccessTagsAndBits.TAXI | AccessTagsAndBits.BUS);
                else if(vehicle.equals("bicycle"))
                        flag = AccessTagsAndBits.BIKE;
                else if(vehicle.equals("motorcar"))
                        flag = AccessTagsAndBits.CAR;
                else if(vehicle.equals("bus"))
                        flag = AccessTagsAndBits.BUS;
                else if(vehicle.equals("taxi"))
                        flag = AccessTagsAndBits.TAXI;
                else if(vehicle.equals("goods"))
                        flag = AccessTagsAndBits.DELIVERY;
                else if(vehicle.equals("hgv") || vehicle.equals("truck"))
                        flag = AccessTagsAndBits.TRUCK;
                else if(vehicle.equals("emergency"))
                        flag = AccessTagsAndBits.EMERGENCY;
                else if(vehicle.equals("foot"))
                        flag = AccessTagsAndBits.FOOT;
                if (flag == 0){
                        log.warn(messagePrefix, "ignoring unsupported vehicle class '" + vehicle + "' in turn restriction");
                        return false;
                }
               
                if (b)
                        exceptMask |= flag;
                else
                        exceptMask &= ~flag;
                return true;                   
        }
       
        public boolean isFromWay(long wayId) {
                return fromWayIds.contains(wayId);
        }

        public boolean isToWay(long wayId) {
                return toWayIds.contains(wayId);
        }

        public void replaceViaCoord(Coord oldP, Coord newP) {
                for (int i = 0; i < viaPoints.size(); i++){
                        if (viaPoints.get(i) == oldP){
                                viaPoints.set(i, newP);
                                if (log.isDebugEnabled()){
                                        log.debug(messagePrefix, restriction, "'via' coord redefined from",
                                                        oldP.toOSMURL(), "to", newP.toOSMURL());
                                }
                                return;
                        }
                }
        }
       
        public void addRestriction(MapCollector collector, IdentityHashMap<Coord, CoordNode> nodeIdMap) {
                if (!valid)
                        return;
            List<CoordNode> viaNodes = new ArrayList<>();
                for (Coord v: viaPoints){
                        CoordNode vn = nodeIdMap.get(v);
                        if (vn == null){
                                log.error(messagePrefix,"via node is not a routing node");
                                return;
                        }
                        viaNodes.add(vn);
                }

                if (viaNodes.size() > 6){
                        log.warn(messagePrefix,"has more than 6 via nodes, this is not supported");
                        return;
                }
                if(restriction == null){
                        log.error("internal error: can't add valid restriction relation", this.getId(), "type", restriction);
                        return;
                }
                int addedRestrictions = 0;
                GeneralRouteRestriction grr;
                if(restriction.startsWith("no_")){
                        for (long fromWayId : fromWayIds){
                                for (long toWayId : toWayIds){
                                        grr = new GeneralRouteRestriction("not", exceptMask, messagePrefix);
                                        grr.setFromWayId(fromWayId);
                                        grr.setToWayId(toWayId);
                                        grr.setViaNodes(viaNodes);
                                        grr.setViaWayIds(viaWayIds);
                                        grr.setDirIndicator(dirIndicator);
                                        addedRestrictions += collector.addRestriction(grr);
                                }
                        }
                        if (log.isInfoEnabled())
                                log.info(messagePrefix, restriction, "translated to",addedRestrictions,"img file restrictions");
                }
                else if(restriction.startsWith("only_")){
                        grr = new GeneralRouteRestriction("only", exceptMask, messagePrefix);
                        grr.setFromWayId(fromWayIds.get(0));
                        grr.setToWayId(toWayIds.get(0));
                        grr.setViaNodes(viaNodes);
                        grr.setViaWayIds(viaWayIds);
                        grr.setDirIndicator(dirIndicator);
                        int numAdded = collector.addRestriction(grr);
                        if (numAdded > 0)
                                log.info(messagePrefix, restriction, "added - allows routing to way", toWayIds.get(0));

                }
                else {
                        log.error("mkgmap internal error: unknown restriction", restriction);
                }
        }

        /** Process the members in this relation.
         */

        public void processElements() {
                // relax
        }

        public String toString() {
                String s = "[restriction id = " + getId() + "(" + restriction + ")";
                if (!fromWayIds.isEmpty() && !toWayIds.isEmpty() && viaCoord != null )
                        s += ", from = " + fromWayIds.get(0) + ", to = " + toWayIds.get(0) + ", via = " + viaCoord.toOSMURL() + "]";
                else
                        s += "]";
                return s;
        }
       
        /**
         * @return true if restriction is usable
         */

        public boolean isValid() {
                assert evalWasCalled;
                return valid;
        }

        public List<Coord> getViaCoords() {
                assert evalWasCalled;
                return viaPoints;
        }

        /**
         *
         * @return a Set with the OSM IDs of all ways used in the restriction
         */

        public Set<Long> getWayIds(){
                assert evalWasCalled;
                Set<Long> wayIds = new HashSet<>();
                wayIds.addAll(fromWayIds);
                wayIds.addAll(viaWayIds);
                wayIds.addAll(toWayIds);
                return wayIds;
        }
       
        public byte getExceptMask(){
                assert evalWasCalled;
                return exceptMask;
        }
       
        /**
         * Replace
         * @param oldWayId
         * @param newWayId
         * @return
         */

        public boolean replaceWay(long oldWayId, long newWayId) {
                assert evalWasCalled;
                boolean matched = false;
                for (List<Long> ways: Arrays.asList(fromWayIds, viaWayIds, toWayIds)){
                        for (int i = 0; i < ways.size(); i++){
                                if (ways.get(i) == oldWayId){
                                        ways.set(i, newWayId);
                                        matched = true;
                                }
                        }
                }
                return matched;
        }

        /**
         * check if restriction is still valid if the way with the given id is not in the map
         * @param wayId
         * @return
         */

        public boolean isValidWithoputWay(long wayId) {
                assert evalWasCalled;
                if (viaWayIds.contains(wayId))
                        return false;
                fromWayIds.remove(wayId);
                if (fromWayIds.isEmpty())
                        return false;
                // else it must be a no_entry restriction which still is valid

                toWayIds.remove(wayId);
                if (toWayIds.isEmpty())
                        return false;
                // else it must be a no_exit restriction which still is valid
                return true;
        }

        /**
         * A via way may be connected to other ways between the end points.
         * We have to create a complete path for that.
         * @param way
         * @param nodeIndices
         */

        public void updateViaWay(Way way, List<Integer> nodeIndices) {
                if (!valid)
                        return;
                if (viaWayIds.contains(way.getId()) == false)
                        return;
                Coord first = way.getPoints().get(nodeIndices.get(0));
                Coord last = way.getPoints().get(
                                nodeIndices.get(nodeIndices.size() - 1));
                int posFirst = -1;
                for (int i = 0; i < viaPoints.size(); i++) {
                        if (first == viaPoints.get(i)) {
                                posFirst = i;
                                break;
                        }
                }
                int posLast = -1;
                for (int i = 0; i < viaPoints.size(); i++) {
                        if (last == viaPoints.get(i)) {
                                posLast = i;
                                break;
                        }
                }
                if (posFirst < 0  || posLast < 0){
                        log.error(messagePrefix, "internal error: via way doesn't contain expected points");
                        valid = false;
                        return;
                }
               
                List<Coord> midPoints = new ArrayList<>();
                for (int i = 1; i + 1 < nodeIndices.size(); i++) {
                        midPoints.add(way.getPoints().get(nodeIndices.get(i)));
                }
                if (posFirst < posLast){
                        if (posLast - posFirst > 1)
                                viaPoints.subList(posFirst+1, posLast).clear();
                        viaPoints.addAll(posFirst + 1, midPoints);
                }
                else {
                        if (posFirst - posLast > 1)
                                viaPoints.subList(posLast + 1, posFirst).clear();
                        Collections.reverse(midPoints);
                        viaPoints.addAll(posLast + 1, midPoints);
                }
               
                int wayPos = viaWayIds.indexOf(way.getId());
                while(viaWayIds.size() > wayPos + 1 && viaWayIds.get(wayPos+1) == way.getId())
                        viaWayIds.remove(wayPos);
                for (int i = 0; i < midPoints.size(); i++){
                        viaWayIds.add(wayPos+1, way.getId());
                }
                if (viaPoints.size() != viaWayIds.size()+1){
                        log.error("internal error: number of via points and via ways no longer fits");
                        valid = false;
                } else if (viaPoints.size() > 6){
                        log.warn(messagePrefix,"has more than 6 via nodes, this is not supported");
                        valid = false;
                }
        }
}