package net.osmand.osm.util; import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; 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.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.JFrame; import javax.swing.JMenuBar; import javax.swing.UIManager; import javax.xml.stream.XMLStreamException; import net.osmand.data.DataTileManager; import net.osmand.impl.ConsoleProgressImplementation; import net.osmand.osm.Entity; import net.osmand.osm.EntityInfo; import net.osmand.osm.LatLon; import net.osmand.osm.MapUtils; import net.osmand.osm.Node; import net.osmand.osm.Relation; import net.osmand.osm.Way; import net.osmand.osm.Entity.EntityId; import net.osmand.osm.Entity.EntityType; import net.osmand.osm.OSMSettings.OSMTagKey; import net.osmand.osm.io.IOsmStorageFilter; import net.osmand.osm.io.OsmBaseStorage; import net.osmand.osm.io.OsmStorageWriter; import net.osmand.swing.DataExtractionSettings; import net.osmand.swing.MapPanel; import org.xml.sax.SAXException; public class MinskTransReader { // Routes RouteNum; Authority; City; Transport; Operator; ValidityPeriods; SpecialDates;RouteTag;RouteType;Commercial;RouteName;Weekdays;RouteID;Entry;RouteStops;Datestart public static class TransportRoute { public String routeNum; // 0 public String transport; // 3 public String routeType; // 8 public String routeName; // 10 public String routeId; // 12 public List<String> routeStops = new ArrayList<String>(); // 14 } // ID;City;Area;Street;Name;Lng;Lat;Stops public static class TransportStop { public String stopId; // 0 public double longitude; // 5 public double latitude; // 6 public String name ; //4 } public static final int default_dist_to_stop = 60; public static final String pathToRoutes = "E:/routes.txt"; public static final String pathToStops = "E:/stops.txt"; public static final String pathToMinsk = "E:\\Information\\OSM maps\\minsk_streets.osm"; public static final String pathToSave = "E:\\Information\\OSM maps\\data_edit.osm"; public static void main(String[] args) throws IOException, SAXException, XMLStreamException { FileInputStream fis = new FileInputStream(new File(pathToRoutes)); BufferedReader reader = new BufferedReader(new InputStreamReader(fis, Charset.forName("UTF-8"))); List<TransportRoute> routes = readRoutes(reader); fis = new FileInputStream(new File(pathToStops)); reader = new BufferedReader(new InputStreamReader(fis, Charset.forName("UTF-8"))); List<TransportStop> stops = readStopes(reader); Map<String, TransportStop> stopsMap = new LinkedHashMap<String, TransportStop>(); for(TransportStop s : stops){ stopsMap.put(s.stopId, s); } // checking that stops are good for(TransportRoute r : routes){ for(String string : r.routeStops){ if(!stopsMap.containsKey(string)){ throw new IllegalArgumentException("Check stop " + string + " of route " + r.routeName); } } } // showMapPanelWithCorrelatedBusStops(stopsMap, busStops); OsmBaseStorage storage = filterBusStops(stopsMap, routes); OsmStorageWriter writer = new OsmStorageWriter(); writer.saveStorage(new FileOutputStream(pathToSave), storage, null, true); } public static void showMapPanelWithCorrelatedBusStops(Map<String, TransportStop> stopsMap, DataTileManager<Node> busStops) { Map<String, Node> result = correlateExistingBusStopsWithImported(busStops, stopsMap); DataTileManager<Entity> nodes = new DataTileManager<Entity>(); for (String trId : result.keySet()) { TransportStop r = stopsMap.get(trId); Way way = new Way(-1); way.addNode(result.get(trId)); way.addNode(new Node(r.latitude, r.longitude, -1)); nodes.registerObject(r.latitude, r.longitude, way); } for(String trId : stopsMap.keySet()){ if(!result.containsKey(trId)){ TransportStop r = stopsMap.get(trId); nodes.registerObject(r.latitude, r.longitude, new Node(r.latitude, r.longitude, -1)); } } showMapPanel(nodes); } public static Map<String, Node> correlateExistingBusStopsWithImported(DataTileManager<Node> busStops, Map<String, TransportStop> stopsMap){ Map<String, Node> correlated = new LinkedHashMap<String, Node>(); Map<Node, String> reverse = new LinkedHashMap<Node, String>(); List<TransportStop> stopsToCheck = new ArrayList<TransportStop>(stopsMap.values()); for(int k =0; k<stopsToCheck.size(); k++){ TransportStop r = stopsToCheck.get(k); List<Node> closestObjects = busStops.getClosestObjects(r.latitude, r.longitude, 0, 1); // filter closest objects for(int i=0; i<closestObjects.size(); ){ if(MapUtils.getDistance(closestObjects.get(i), r.latitude, r.longitude) > default_dist_to_stop){ closestObjects.remove(i); } else{ i++; } } MapUtils.sortListOfEntities(closestObjects, r.latitude, r.longitude); int ind = 0; boolean ccorrelated = false; while(ind < closestObjects.size() && !ccorrelated){ Node foundNode = closestObjects.get(ind); if(!reverse.containsKey(foundNode)){ // all is good no one registered to that stop reverse.put(foundNode, r.stopId); correlated.put(r.stopId, foundNode); ccorrelated = true; } else { // recorrelate existing node and add to todo list String stopId = reverse.get(foundNode); TransportStop st = stopsMap.get(stopId); if(MapUtils.getDistance(foundNode, r.latitude, r.longitude) < MapUtils.getDistance(foundNode, st.latitude, st.longitude)){ // check that stop again stopsToCheck.add(st); reverse.put(foundNode, r.stopId); correlated.put(r.stopId, foundNode); correlated.remove(st.stopId); ccorrelated = true; } } ind++; } } return correlated; } public static void showMapPanel(DataTileManager<? extends Entity> points){ JFrame frame = new JFrame("Map view"); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } final MapPanel panel = new MapPanel(DataExtractionSettings.getSettings().getTilesDirectory()); panel.setPoints(points); frame.addWindowListener(new WindowAdapter(){ @Override public void windowClosing(WindowEvent e) { DataExtractionSettings settings = DataExtractionSettings.getSettings(); settings.saveDefaultLocation(panel.getLatitude(), panel.getLongitude()); settings.saveDefaultZoom(panel.getZoom()); System.exit(0); } }); Container content = frame.getContentPane(); content.add(panel, BorderLayout.CENTER); JMenuBar bar = new JMenuBar(); bar.add(MapPanel.getMenuToChooseSource(panel)); frame.setJMenuBar(bar); frame.setSize(512, 512); frame.setVisible(true); } protected static void removeGeneratedNotUsedBusStops(Map<String, Node> correlated, Map<String, Relation> definedRoutes, DataTileManager<Node> busStops, OsmBaseStorage storage){ Set<Node> usedNodes = new LinkedHashSet<Node>(correlated.values()); for(Relation r : definedRoutes.values()){ for(Entity e : r.getMembers(null)){ if(e instanceof Node){ usedNodes.add((Node) e); } } } for(Node stop : busStops.getAllObjects()){ if(!usedNodes.contains(stop) && "yes".equals(stop.getTag("generated"))){ EntityInfo info = storage.getRegisteredEntityInfo().get(stop.getId()); info.setAction("delete"); System.out.println("[DEL] Remove generated not used stop " + stop.getId() + " " + stop.getTag("name")); } } } protected static OsmBaseStorage filterBusStops(Map<String, TransportStop> stopsMap, List<TransportRoute> routes) throws FileNotFoundException, IOException, SAXException{ long time = System.currentTimeMillis(); System.out.println("Start : "); OsmBaseStorage storage = new OsmBaseStorage(); final Map<String, Relation> definedRoutes = new HashMap<String, Relation>(); final DataTileManager<Node> busStops = new DataTileManager<Node>(); busStops.setZoom(17); storage.getFilters().add(new IOsmStorageFilter(){ @Override public boolean acceptEntityToLoad(OsmBaseStorage storage, EntityId entityId, Entity entity) { if(entity.getTag("route") != null){ String route = entity.getTag("route"); if(route.equals("bus") || route.equals("tram") || route.equals("trolleybus") || route.equals("subway")){ definedRoutes.put(entity.getTag("route") + "_" + entity.getTag("ref"), (Relation) entity); return true; } } if(entity.getTag(OSMTagKey.HIGHWAY) != null && entity.getTag(OSMTagKey.HIGHWAY).equals("bus_stop")){ LatLon e = entity.getLatLon(); busStops.registerObject(e.getLatitude(), e.getLongitude(), (Node) entity); } return entity instanceof Node; } }); storage.parseOSM(new FileInputStream(pathToMinsk), new ConsoleProgressImplementation()); Map<String, Node> correlated = correlateExistingBusStopsWithImported(busStops, stopsMap); removeGeneratedNotUsedBusStops(correlated, definedRoutes, busStops, storage); registerNewRoutesAndEditExisting(stopsMap, routes, storage, definedRoutes, correlated); System.out.println("End time : " + (System.currentTimeMillis() - time)); return storage; } protected static boolean validateRoute(String routeStr, Map<String, TransportStop> trStops, Map<String, Node> correlated, Relation relation, TransportRoute route, boolean direct){ Collection<Entity> stops = relation.getMembers("stop"); routeStr += direct ? "_forward" : "_backward"; if(stops.size() != 2){ System.out.println("[INVALID ] " + routeStr + " : doesn't contain start/final stop."); return false; } List<Entity> list = new ArrayList<Entity>(relation.getMembers(direct?"forward:stop" : "backward:stop")); if((list.size() + 2) != route.routeStops.size()){ System.out.println("[INVALID ] " + routeStr + " number of stops isn't equal (" + ((list.size() + 2)) + " relation != " + route.routeStops.size() + " route) "); return false; } Iterator<Entity> it = stops.iterator(); Entity start = it.next(); Entity end = it.next(); if(direct){ list.add(0, start); list.add(end); } else { list.add(0, end); list.add(start); } for(int i=0; i<list.size(); i++){ String st = route.routeStops.get(i); Node correlatedNode = correlated.get(st); TransportStop trStop = trStops.get(st); String stStr = trStop.stopId + " " + trStop.name; Entity e = list.get(i); if(correlatedNode == null){ double dist = MapUtils.getDistance(e.getLatLon(), trStop.latitude, trStop.longitude); if(dist > 20){ System.out.println("[INVALID ]" + routeStr + " stop " + (i+1) + " was not correlated " + stStr + " distance = " + dist); return false; } } else if(correlatedNode.getId() != e.getId()){ double dist = MapUtils.getDistance(correlatedNode, e.getLatLon()); if(i==list.size() - 1 && !direct && dist < 150){ continue; } String eStop = e.getId() + " " + e.getTag(OSMTagKey.NAME); System.out.println("[INVALID ] " + routeStr + " stop " + (i+1) + " wrong : " + stStr + " != " + eStop + " dist = " + dist + " current correlated to " + correlatedNode.getId()); return false; } } return true; } protected static long id = -55000; protected static void registerNewRoutesAndEditExisting(Map<String, TransportStop> stopsMap, List<TransportRoute> routes, OsmBaseStorage storage, final Map<String, Relation> definedRoutes, Map<String, Node> correlated) { Map<String, Relation> checkedRoutes = new LinkedHashMap<String, Relation>(); // because routes can changed on schedule that's why for 1 relation many routes. Set<String> visitedRoutes = new HashSet<String>(); for (TransportRoute r : routes) { // register only bus/trolleybus if (!r.transport.equals("bus") && !r.transport.equals("trolleybus")) { continue; } String s = r.transport + "_" + r.routeNum; boolean reverse = r.routeType.equals("B>A"); boolean direct = r.routeType.equals("A>B"); if (!reverse && !direct) { // that's additinal route skip it continue; } if (!visitedRoutes.add(s + "_" + direct)) { // skip it : duplicated route (schedule changed) continue; } if (definedRoutes.containsKey(s)) { checkedRoutes.put(s, definedRoutes.get(s)); boolean valid = validateRoute(s, stopsMap, correlated, definedRoutes.get(s), r, direct); if(valid){ System.err.println("VALID " + s + " " + direct); } // System.out.println("Already registered " + s); } else { if (!checkedRoutes.containsKey(s)) { if(reverse){ System.err.println("Strange route skipped : " + s); continue; } Relation relation = new Relation(id--); relation.putTag("route", r.transport); relation.putTag("ref", r.routeNum); relation.putTag("name", r.routeName); relation.putTag("operator", "КУП \"Минсктранс\""); relation.putTag("type", "route"); relation.putTag("generated", "yes"); checkedRoutes.put(s, relation); storage.getRegisteredEntities().put(new EntityId(EntityType.RELATION, relation.getId()), relation); System.out.println("[ADD] Registered new route " + s); } Relation relation = checkedRoutes.get(s); // correlating stops for (int i = 0; i < r.routeStops.size(); i++) { String stop = r.routeStops.get(i); if (!stopsMap.containsKey(stop)) { throw new IllegalArgumentException("Stops file is not corresponded to routes file"); } if (!correlated.containsKey(stop)) { TransportStop st = stopsMap.get(stop); Node node = new Node(st.latitude, st.longitude, id--); node.putTag("highway", "bus_stop"); if (st.name != null) { node.putTag("name", st.name); } else { throw new IllegalArgumentException("Something wrong check " + st.stopId); } node.putTag("generated", "yes"); storage.getRegisteredEntities().put(new EntityId(EntityType.NODE, node.getId()), node); System.out.println("[ADD] Added new bus_stop : " + node.getId() + " " + st.name + " minsktrans_stop_id " + st.stopId); correlated.put(stop, node); } if (i == 0 || i == r.routeStops.size() - 1) { if (direct) { relation.addMember(correlated.get(stop).getId(), EntityType.NODE, "stop"); } } else { if (direct) { relation.addMember(correlated.get(stop).getId(), EntityType.NODE, "forward:stop"); } else { relation.addMember(correlated.get(stop).getId(), EntityType.NODE, "backward:stop"); } } } } } // check relations that are not exist for(String s : definedRoutes.keySet()){ if(!checkedRoutes.containsKey(s)){ Relation rel = definedRoutes.get(s); storage.getRegisteredEntityInfo().get(rel.getId()).setAction("delete"); System.out.println("[DEL] Route is deprecated : " + rel.getTag("route")+"_"+rel.getTag("ref") + " " + rel.getTag("name")); } } } protected static List<TransportRoute> readRoutes(BufferedReader reader) throws IOException { String st = null; int line = 0; TransportRoute previous = null; List<TransportRoute> routes = new ArrayList<TransportRoute>(); while((st = reader.readLine()) != null){ if(line++ == 0){ continue; } TransportRoute current = new TransportRoute(); int stI=0; int endI = 0; int i=0; while ((endI = st.indexOf(';', stI)) != -1) { String newS = st.substring(stI, endI); if(i==0){ if(newS.length() > 0){ current.routeNum = newS; } else if(previous != null){ current.routeNum = previous.routeNum; } } else if(i==3){ if(newS.length() > 0){ current.transport = newS; } else if(previous != null){ current.transport = previous.transport; } } else if(i==8){ if(newS.length() > 0){ current.routeType = newS; } else if(previous != null){ current.routeType = previous.routeType ; } } else if(i==10){ if(newS.length() > 0){ current.routeName = newS; } else if(previous != null){ current.routeName = previous.routeName ; } } else if(i==12){ current.routeId = newS; } else if(i==14){ String[] strings = newS.split(","); for(String s : strings){ s = s.trim(); if(s.length() > 0){ current.routeStops.add(s); } } } stI = endI + 1; i++; } previous = current; routes.add(current); } return routes; } protected static List<TransportStop> readStopes(BufferedReader reader) throws IOException { String st = null; int line = 0; List<TransportStop> stopes = new ArrayList<TransportStop>(); TransportStop previous = null; while((st = reader.readLine()) != null){ if(line++ == 0){ continue; } TransportStop current = new TransportStop(); int stI=0; int endI = 0; int i=0; while ((endI = st.indexOf(';', stI)) != -1) { String newS = st.substring(stI, endI); if(i==0){ current.stopId = newS.trim(); } else if(i==4){ if(newS.length() == 0 && previous != null){ current.name = previous.name; } else { current.name = newS; } } else if(i==5){ current.longitude = Double.parseDouble(newS)/1e5; } else if(i==6){ current.latitude = Double.parseDouble(newS)/1e5; } stI = endI + 1; i++; } previous = current; stopes.add(current); } return stopes; } }