All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.graphhopper.ui.MiniGraphUI Maven / Gradle / Ivy

There is a newer version: 10.0
Show newest version
/*
 *  Licensed to GraphHopper GmbH under one or more contributor
 *  license agreements. See the NOTICE file distributed with this work for
 *  additional information regarding copyright ownership.
 *
 *  GraphHopper GmbH licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.graphhopper.ui;

import com.carrotsearch.hppc.IntIndexedContainer;
import com.graphhopper.GraphHopper;
import com.graphhopper.GraphHopperConfig;
import com.graphhopper.coll.GHBitSet;
import com.graphhopper.coll.GHTBitSet;
import com.graphhopper.config.CHProfile;
import com.graphhopper.config.LMProfile;
import com.graphhopper.config.Profile;
import com.graphhopper.routing.*;
import com.graphhopper.routing.ev.BooleanEncodedValue;
import com.graphhopper.routing.ev.DecimalEncodedValue;
import com.graphhopper.routing.ev.VehicleAccess;
import com.graphhopper.routing.ev.VehicleSpeed;
import com.graphhopper.routing.lm.LMRoutingAlgorithmFactory;
import com.graphhopper.routing.lm.LandmarkStorage;
import com.graphhopper.routing.querygraph.QueryGraph;
import com.graphhopper.routing.querygraph.QueryRoutingCHGraph;
import com.graphhopper.routing.util.AllEdgesIterator;
import com.graphhopper.routing.util.EdgeFilter;
import com.graphhopper.routing.util.TraversalMode;
import com.graphhopper.routing.weighting.custom.CustomModelParser;
import com.graphhopper.storage.BaseGraph;
import com.graphhopper.storage.NodeAccess;
import com.graphhopper.storage.RoutingCHGraph;
import com.graphhopper.storage.index.LocationIndexTree;
import com.graphhopper.storage.index.Snap;
import com.graphhopper.util.FetchMode;
import com.graphhopper.util.PMap;
import com.graphhopper.util.Parameters.Algorithms;
import com.graphhopper.util.PointList;
import com.graphhopper.util.StopWatch;
import com.graphhopper.util.shapes.BBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.Arrays;
import java.util.Random;

/**
 * A rough graphical user interface for visualizing the OSM graph. Mainly for debugging algorithms
 * and spatial data structures. See e.g. this blog post:
 * https://graphhopper.com/blog/2016/01/19/alternative-roads-to-rome/
 * 

* Use the web module for a better/faster/userfriendly/... alternative! *

* * @author Peter Karich */ public class MiniGraphUI { private final Logger logger = LoggerFactory.getLogger(getClass()); private final BaseGraph graph; private final NodeAccess na; private final MapLayer pathLayer; private final DecimalEncodedValue avSpeedEnc; private final BooleanEncodedValue accessEnc; private final boolean useCH; // for moving int currentPosX; int currentPosY; private LocationIndexTree index; private String latLon = ""; private GraphicsWrapper mg; private JPanel infoPanel; private LayeredPanel mainPanel; private MapLayer roadsLayer; private boolean fastPaint = false; private boolean showQuadTree = false; private Snap fromRes; private Snap toRes; private QueryGraph qGraph; public static void main(String[] strs) { PMap args = PMap.read(strs); args.putObject("datareader.file", args.getString("datareader.file", "core/files/monaco.osm.gz")); args.putObject("graph.location", args.getString("graph.location", "tools/target/mini-graph-ui-gh")); GraphHopperConfig ghConfig = new GraphHopperConfig(args); ghConfig.setProfiles(Arrays.asList( new Profile("profile") .setVehicle("car") .setTurnCosts(true) )).putObject("import.osm.ignored_highways", ""); ghConfig.setCHProfiles(Arrays.asList( new CHProfile("profile") )); ghConfig.setLMProfiles(Arrays.asList( new LMProfile("profile") )); GraphHopper hopper = new GraphHopper().init(ghConfig).importOrLoad(); boolean debug = args.getBool("minigraphui.debug", false); boolean useCH = args.getBool("minigraphui.useCH", false); new MiniGraphUI(hopper, debug, useCH).visualize(); } public MiniGraphUI(GraphHopper hopper, boolean debug, boolean useCH) { this.graph = hopper.getBaseGraph(); this.na = graph.getNodeAccess(); String vehicle = hopper.getProfiles().get(0).getVehicle(); accessEnc = hopper.getEncodingManager().getBooleanEncodedValue(VehicleAccess.key(vehicle)); avSpeedEnc = hopper.getEncodingManager().getDecimalEncodedValue(VehicleSpeed.key(vehicle)); this.useCH = useCH; logger.info("locations:" + graph.getNodes() + ", debug:" + debug); mg = new GraphicsWrapper(graph); this.index = (LocationIndexTree) hopper.getLocationIndex(); infoPanel = new JPanel() { @Override protected void paintComponent(Graphics g) { g.setColor(Color.WHITE); Rectangle b = infoPanel.getBounds(); g.fillRect(0, 0, b.width, b.height); g.setColor(Color.BLUE); g.drawString(latLon, 40, 20); g.drawString("scale:" + mg.getScaleX(), 40, 40); int w = mainPanel.getBounds().width; int h = mainPanel.getBounds().height; g.drawString(mg.setBounds(0, w, 0, h).toLessPrecisionString(), 40, 60); } }; mainPanel = new LayeredPanel(); // TODO make it correct with bitset-skipping too final GHBitSet bitset = new GHTBitSet(graph.getNodes()); mainPanel.addLayer(roadsLayer = new DefaultMapLayer() { final Random rand = new Random(); @Override public void paintComponent(final Graphics2D g2) { clearGraphics(g2); Rectangle d = getBounds(); BBox b = mg.setBounds(0, d.width, 0, d.height); if (fastPaint) { rand.setSeed(0); bitset.clear(); } g2.setColor(Color.black); Color[] speedColors = generateColors(15); AllEdgesIterator edge = graph.getAllEdges(); while (edge.next()) { if (fastPaint && rand.nextInt(30) > 1) continue; int nodeIndex = edge.getBaseNode(); double lat = na.getLat(nodeIndex); double lon = na.getLon(nodeIndex); int nodeId = edge.getAdjNode(); double lat2 = na.getLat(nodeId); double lon2 = na.getLon(nodeId); // mg.plotText(g2, lat, lon, "" + nodeIndex); if (!b.contains(lat, lon) && !b.contains(lat2, lon2)) continue; int sum = nodeIndex + nodeId; if (fastPaint) { if (bitset.contains(sum)) continue; bitset.add(sum); } // mg.plotText(g2, lat * 0.9 + lat2 * 0.1, lon * 0.9 + lon2 * 0.1, iter.getName()); //mg.plotText(g2, lat * 0.9 + lat2 * 0.1, lon * 0.9 + lon2 * 0.1, "s:" + (int) encoder.getSpeed(iter.getFlags())); double speed = edge.get(avSpeedEnc); Color color; if (speed >= 120) { // red color = speedColors[12]; } else if (speed >= 100) { color = speedColors[10]; } else if (speed >= 80) { color = speedColors[8]; } else if (speed >= 60) { color = speedColors[6]; } else if (speed >= 50) { color = speedColors[5]; } else if (speed >= 40) { color = speedColors[4]; } else if (speed >= 30) { color = Color.GRAY; } else { color = Color.LIGHT_GRAY; } g2.setColor(color); boolean fwd = edge.get(accessEnc); boolean bwd = edge.getReverse(accessEnc); float width = speed > 90 ? 1f : 0.8f; PointList pl = edge.fetchWayGeometry(FetchMode.ALL); for (int i = 1; i < pl.size(); i++) { if (fwd && !bwd) { mg.plotDirectedEdge(g2, pl.getLat(i - 1), pl.getLon(i - 1), pl.getLat(i), pl.getLon(i), width); } else { mg.plotEdge(g2, pl.getLat(i - 1), pl.getLon(i - 1), pl.getLat(i), pl.getLon(i), width); } } } if (showQuadTree) index.query(graph.getBounds(), new LocationIndexTree.Visitor() { @Override public boolean isTileInfo() { return true; } @Override public void onTile(BBox bbox, int depth) { int width = Math.max(1, Math.min(4, 4 - depth)); g2.setColor(Color.GRAY); mg.plotEdge(g2, bbox.minLat, bbox.minLon, bbox.minLat, bbox.maxLon, width); mg.plotEdge(g2, bbox.minLat, bbox.maxLon, bbox.maxLat, bbox.maxLon, width); mg.plotEdge(g2, bbox.maxLat, bbox.maxLon, bbox.maxLat, bbox.minLon, width); mg.plotEdge(g2, bbox.maxLat, bbox.minLon, bbox.minLat, bbox.minLon, width); } @Override public void onEdge(int edgeId) { // mg.plotNode(g2, node, Color.BLUE); } }); g2.setColor(Color.WHITE); g2.fillRect(0, 0, 1000, 20); for (int i = 4; i < speedColors.length; i++) { g2.setColor(speedColors[i]); g2.drawString("" + (i * 10), i * 30 - 100, 10); } g2.setColor(Color.BLACK); } }); mainPanel.addLayer(pathLayer = new DefaultMapLayer() { @Override public void paintComponent(final Graphics2D g2) { if (qGraph == null) return; makeTransparent(g2); RoutingAlgorithm algo = createAlgo(hopper, qGraph); if (algo instanceof DebugAlgo) { ((DebugAlgo) algo).setGraphics2D(g2); } StopWatch sw = new StopWatch().start(); logger.info("start searching with " + algo + " from:" + fromRes + " to:" + toRes); Color red = Color.red.brighter(); g2.setColor(red); mg.plotNode(g2, qGraph.getNodeAccess(), fromRes.getClosestNode(), red, 10, ""); mg.plotNode(g2, qGraph.getNodeAccess(), toRes.getClosestNode(), red, 10, ""); g2.setColor(Color.blue.brighter().brighter()); java.util.List paths = algo.calcPaths(fromRes.getClosestNode(), toRes.getClosestNode()); sw.stop(); // if directed edges if (paths.isEmpty() || !paths.get(0).isFound()) { logger.warn("path not found! direction not valid?"); return; } Path best = paths.get(0); logger.info("found path in " + sw.getSeconds() + "s with nodes:" + best.calcNodes().size() + ", millis: " + best.getTime() + ", visited nodes:" + algo.getVisitedNodes()); g2.setColor(red); for (Path p : paths) plotPath(p, g2, 3); } }); if (debug) { // disable double buffering to see graphic changes while debugging. E.g. to set a break point in the // algorithm its updateBest method and see the shortest path tree increasing everytime the program continues. RepaintManager repaintManager = RepaintManager.currentManager(mainPanel); repaintManager.setDoubleBufferingEnabled(false); mainPanel.setBuffering(false); } } private RoutingAlgorithm createAlgo(GraphHopper hopper, QueryGraph qGraph) { Profile profile = hopper.getProfiles().iterator().next(); if (useCH) { RoutingCHGraph chGraph = hopper.getCHGraphs().get(profile.getName()); logger.info("CH algo, profile: " + profile.getName()); QueryRoutingCHGraph queryRoutingCHGraph = new QueryRoutingCHGraph(chGraph, qGraph); return new CHDebugAlgo(queryRoutingCHGraph, mg); } else { LandmarkStorage landmarks = hopper.getLandmarks().get(profile.getName()); RoutingAlgorithmFactory algoFactory = (g, w, opts) -> { RoutingAlgorithm algo = new LMRoutingAlgorithmFactory(landmarks).createAlgo(g, w, opts); if (algo instanceof AStarBidirection) { return new DebugAStarBi(g, w, opts.getTraversalMode(), mg). setApproximation(((AStarBidirection) algo).getApproximation()); } else if (algo instanceof AStar) { return new DebugAStar(g, w, opts.getTraversalMode(), mg); } else if (algo instanceof DijkstraBidirectionRef) { return new DebugDijkstraBidirection(g, w, opts.getTraversalMode(), mg); } else if (algo instanceof Dijkstra) { return new DebugDijkstraSimple(g, w, opts.getTraversalMode(), mg); } else return algo; }; AlgorithmOptions algoOpts = new AlgorithmOptions().setAlgorithm(Algorithms.ASTAR_BI). setTraversalMode(TraversalMode.EDGE_BASED); return algoFactory.createAlgo(qGraph, CustomModelParser.createFastestWeighting(accessEnc, avSpeedEnc, hopper.getEncodingManager()), algoOpts); } } private static class CHDebugAlgo extends DijkstraBidirectionCH implements DebugAlgo { private final GraphicsWrapper mg; private Graphics2D g2; public CHDebugAlgo(RoutingCHGraph graph, GraphicsWrapper mg) { super(graph); this.mg = mg; } @Override public void setGraphics2D(Graphics2D g2) { this.g2 = g2; } @Override public void updateBestPath(double edgeWeight, SPTEntry entry, int origEdgeId, int traversalId, boolean reverse) { if (g2 != null) mg.plotNode(g2, traversalId, Color.YELLOW, 6); super.updateBestPath(edgeWeight, entry, origEdgeId, traversalId, reverse); } } public Color[] generateColors(int n) { Color[] cols = new Color[n]; for (int i = 0; i < n; i++) { cols[i] = Color.getHSBColor((float) i / (float) n, 0.85f, 1.0f); } return cols; } void plotNodeName(Graphics2D g2, int node) { double lat = na.getLat(node); double lon = na.getLon(node); mg.plotText(g2, lat, lon, "" + node); } private Path plotPath(Path tmpPath, Graphics2D g2, int w) { if (!tmpPath.isFound()) { logger.info("cannot plot path as not found: " + tmpPath); return tmpPath; } double prevLat = Double.NaN; double prevLon = Double.NaN; boolean plotNodes = false; IntIndexedContainer nodes = tmpPath.calcNodes(); if (plotNodes) { for (int i = 0; i < nodes.size(); i++) { plotNodeName(g2, nodes.get(i)); } } PointList list = tmpPath.calcPoints(); for (int i = 0; i < list.size(); i++) { double lat = list.getLat(i); double lon = list.getLon(i); if (!Double.isNaN(prevLat)) { mg.plotEdge(g2, prevLat, prevLon, lat, lon, w); } else { mg.plot(g2, lat, lon, w); } prevLat = lat; prevLon = lon; } logger.info("dist:" + tmpPath.getDistance() + ", path points(" + list.size() + ")"); return tmpPath; } public void visualize() { try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { int frameHeight = 800; int frameWidth = 1200; JFrame frame = new JFrame("GraphHopper UI - Small&Ugly ;)"); frame.setLayout(new BorderLayout()); frame.add(mainPanel, BorderLayout.CENTER); frame.add(infoPanel, BorderLayout.NORTH); infoPanel.setPreferredSize(new Dimension(300, 100)); // scale mainPanel.addMouseWheelListener(new MouseWheelListener() { @Override public void mouseWheelMoved(MouseWheelEvent e) { mg.scale(e.getX(), e.getY(), e.getWheelRotation() < 0); repaintRoads(); } }); // listener to investigate findID behavior // MouseAdapter ml = new MouseAdapter() { // // @Override public void mouseClicked(MouseEvent e) { // findIDLat = mg.getLat(e.getY()); // findIDLon = mg.getLon(e.getX()); // findIdLayer.repaint(); // mainPanel.repaint(); // } // // @Override public void mouseMoved(MouseEvent e) { // updateLatLon(e); // } // // @Override public void mousePressed(MouseEvent e) { // updateLatLon(e); // } // }; MouseAdapter ml = new MouseAdapter() { // for routing: double fromLat, fromLon; boolean fromDone = false; boolean dragging = false; @Override public void mouseClicked(MouseEvent e) { if (!fromDone) { fromLat = mg.getLat(e.getY()); fromLon = mg.getLon(e.getX()); } else { double toLat = mg.getLat(e.getY()); double toLon = mg.getLon(e.getX()); StopWatch sw = new StopWatch().start(); logger.info("start searching from " + fromLat + "," + fromLon + " to " + toLat + "," + toLon); // get from and to node id fromRes = index.findClosest(fromLat, fromLon, EdgeFilter.ALL_EDGES); toRes = index.findClosest(toLat, toLon, EdgeFilter.ALL_EDGES); if (fromRes.isValid() && toRes.isValid()) { qGraph = QueryGraph.create(graph, fromRes, toRes); mg.setNodeAccess(qGraph); logger.info("found ids " + fromRes + " -> " + toRes + " in " + sw.stop().getSeconds() + "s"); } repaintPaths(); } fromDone = !fromDone; } @Override public void mouseDragged(MouseEvent e) { dragging = true; fastPaint = true; update(e); updateLatLon(e); } @Override public void mouseReleased(MouseEvent e) { if (dragging) { // update only if mouse release comes from dragging! (at the moment equal to fastPaint) dragging = false; fastPaint = false; update(e); } } public void update(MouseEvent e) { mg.setNewOffset(e.getX() - currentPosX, e.getY() - currentPosY); repaintRoads(); } @Override public void mouseMoved(MouseEvent e) { updateLatLon(e); } @Override public void mousePressed(MouseEvent e) { updateLatLon(e); } }; mainPanel.addMouseListener(ml); mainPanel.addMouseMotionListener(ml); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(frameWidth + 10, frameHeight + 30); frame.setVisible(true); } }); } catch (Exception ex) { throw new RuntimeException(ex); } } void updateLatLon(MouseEvent e) { latLon = mg.getLat(e.getY()) + "," + mg.getLon(e.getX()); infoPanel.repaint(); currentPosX = e.getX(); currentPosY = e.getY(); } void repaintPaths() { pathLayer.repaint(); mainPanel.repaint(); } void repaintRoads() { // avoid threading as there should be no updated to scale or offset while painting // (would to lead to artifacts) StopWatch sw = new StopWatch().start(); pathLayer.repaint(); roadsLayer.repaint(); mainPanel.repaint(); logger.info("roads painting took " + sw.stop().getSeconds() + " sec"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy