Rev 322 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* Copyright (C) 2014 Steve Ratcliffe
*
* 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 test.svg;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import org.dom4j.Element;
import test.display.CommonDisplay;
import test.elements.Line;
import test.files.NodFile;
import test.files.RouteArc;
import test.files.RouteCenter;
import test.files.RouteNode;
/**
* Convert a nod file to svg to show the routing relationships.
*
* @author Steve Ratcliffe
*/
public class Route extends CommonDisplay {
private SvgDoc doc;
private Element route;
private Element info;
private Element roads;
private Element spokes;
private NodFile nodFile;
private final Map<Integer, List<Line>> roadMap = new HashMap<>();
private final List<Integer> roadIds = new ArrayList<>();
protected void print() {
openLbl();
openTre();
openNet();
net.setLabels(lbl);
net.setCities(lbl.getCities());
net.setZips(lbl.getZips());
openRgn();
rgn.setNetFile(net);
reader.position(0);
nodFile = new NodFile(reader);
List<RouteCenter> routeCenters = nodFile.readNodeCenters();
nodFile.fixMissingNodes();
initRoads();
for (RouteCenter center: routeCenters)
findMoreNodes(center);
for (RouteCenter center: routeCenters)
findMoreNodes(center);
int max = 4;
int min = max - 3;
if (routeCenters.size() < max)
max = routeCenters.size();
if (max <= min)
min = 0;
for (int i = min; i < max; i++) {
RouteCenter c2 = routeCenters.get(i);
displayCenter(c2);
}
drawRoads();
}
/**
* Go through the nodes we have already and add the linked node if we don't have it.
*
* @param center All the nodes we know about.
*/
private void findMoreNodes(RouteCenter center) {
List<Integer> needed = new ArrayList<>();
for (RouteNode node : center.nodes().values()) {
if (node.isBad())
continue;
for (RouteArc arc : node.getArcs()) {
int off = arc.getLink();
if (nodFile.getNode(off) == null)
needed.add(off);
}
}
for (Integer i : needed) {
if (nodFile.getNode(i) == null) nodFile.fixNode(i);
}
nodFile.resort();
}
private void initRoads() {
Subdivision[] subdivisions = tre.subdivForLevel(0);
for (Subdivision sub : subdivisions) {
List<Line> lines = rgn.linesForSubdiv(sub);
for (Line line : lines) {
int net1Offset = line.getNet1Offset();
// Take this to be a road if it has a NET1 Offset
if (net1Offset > 0) {
List<Line> larray = roadMap.get(net1Offset);
if (larray == null) {
larray = new ArrayList<>();
roadMap.put(net1Offset, larray);
}
larray.add(line);
}
}
}
}
private void drawRoads() {
Collections.sort(roadIds);
int lastId = -1;
for (Integer i : roadIds) {
if (i == lastId)
continue;
lastId = i;
List<Line> lines = roadMap.get(i);
if (lines == null) {
if (i != 0)
System.out.printf("No lines for road id %x\n", i);
continue;
}
for (Line line : lines) {
drawLine(line);
}
}
}
private void displayCenter(RouteCenter c2) {
XY xy1 = toXY(c2.getCoord());
route.addElement("circle")
.addAttribute("cx", xy1.getX())
.addAttribute("cy", xy1.getY())
.addAttribute("r", "8")
.addAttribute("fill", "none")
.addAttribute("stroke", "orange")
.addAttribute("stroke-width", "2");
for (RouteNode node : c2.nodes().values()) {
drawRouteNode(node);
if (node.hasArcs()) {
for (RouteArc arc : node.getArcs()) {
if (arc.isBad())
continue;
drawArc(arc);
}
}
}
}
private void drawRouteNode(RouteNode node) {
XY xy = toXY(node.getCoord());
Element circ = route.addElement("circle")
.addAttribute("cx", xy.getX())
.addAttribute("cy", xy.getY())
.addAttribute("r", "5")
;
XY c = toXY(node.getCenter().getCoord());
spokes.addElement("line")
.addAttribute("x1", xy.getX())
.addAttribute("y1", xy.getY())
.addAttribute("x2", c.getX())
.addAttribute("y2", c.getY())
.addAttribute("stroke", "orange")
.addAttribute("stroke-width", "0.5")
;
if (node.isBad())
circ.addAttribute("fill", "red");
else if (node.isProblem())
circ.addAttribute("fill", "#dd9511");
Element g = info.addElement("g")
.addAttribute("transform",
String.format("translate(%s,%s)", xy.getX(), xy.getY()));
g.addElement("rect")
.addAttribute("width", "50")
.addAttribute("height", "10")
.addAttribute("opacity", "0.8")
.addAttribute("fill", "white")
.addAttribute("stroke-width", "0.5")
.addAttribute("stroke", "black");
Element text = g.addElement("text")
.addAttribute("x", "1")
.addAttribute("y", "5")
.addAttribute("font-size", "3px");
text.addElement("tspan")
.addAttribute("sodipodi:role", "line")
.setText(String.format("0x%x %s", node.getOffset(), xy.toString()));
text.addElement("tspan")
.addAttribute("sodipodi:role", "line")
.setText(String.format("%s, narc=%d", node.flagString(), node.getArcs().size()));
}
private void drawArc(RouteArc arc) {
Integer netOffset = arc.getNetOffset();
if (netOffset != null)
roadIds.add(netOffset);
String colour = "yellow";
switch (arc.getIndex()) {
case 0:
colour = "black";
break;
case 1:
colour = "blue";
break;
case 2:
colour = "green";
break;
}
RouteNode n1 = arc.getNode();
RouteNode n2 = nodFile.getNode(arc.getLink());
if (n2 != null) {
// Draw an arrow half way to the target node.
XY p1 = toXY(n1.getCoord());
XY p2 = toXY(n2.getCoord());
int x2 = p1.getXi() + (p2.getXi() - p1.getXi())/2;
int y2 = p1.getYi() + (p2.getYi() - p1.getYi())/2;
route.addElement("line")
.addAttribute("x1", p1.getX())
.addAttribute("y1", p1.getY())
.addAttribute("x2", String.valueOf(x2))
.addAttribute("y2", String.valueOf(y2))
.addAttribute("stroke", colour)
.addAttribute("stroke-wdith", "1")
.addAttribute("style", "marker-end:url(#Arrow2Send)")
;
if (arc.getIndex() > 0)
return;
// Add a marker pointing the direction we think the arc goes in...
//markDirection(n1.getCoord(), n1.getCoord().bearingTo(n2.getCoord()), 70, colour, true); // test routine
if (arc.hasDirection()) {
XY tip = markDirection(n1.getCoord(), arc.getDirection(), 50, colour, true);
Element g = route.addElement("g")
.addAttribute("transform",
String.format("translate(%s,%s)", tip.getX(), tip.getY()));
Element arcInfo = g.addElement("rect")
.addAttribute("width", "60")
.addAttribute("height", "6")
.addAttribute("fill", "#dddddd")
.addAttribute("opacity", "0.7");
Element text = g.addElement("text")
.addAttribute("x", "1")
.addAttribute("y", "4")
.addAttribute("font-size", "3px")
;
text.addElement("tspan")
.setText(String.format("%d: to %x len=%dm a=%.0fdeg(%2x)", arc.getIndex(),
arc.getLink(), arc.getDistance(), arc.getDirection(),
arc.getRawDirection()));
//text.addElement("tspan")
// .addAttribute("x", "1")
// .addAttribute("dy", "4px")
// .setText(String.format("%d: to %x len=%dm a=%.0fdeg", arc.getIndex(),
// arc.getLink(), arc.getDistance(), arc.getDirection()));
}
}
}
private void drawLine(Line line) {
List<Coord> coords = line.getCoords();
Formatter fmt = new Formatter();
for (int i = 0; i < coords.size(); i++) {
Coord c = coords.get(i);
XY co = toXY(c);
if (i == 0) {
fmt.format("M %d %d", co.getXi(), co.getYi());
} else {
fmt.format(" L %d %d", co.getXi(), co.getYi());
}
}
String colour;
switch (line.getType()) {
case 1: colour = "blue"; break;
case 2: colour = "green"; break;
case 3: colour = "red"; break;
default: colour = "#775599"; break;
}
roads.addElement("path")
.addAttribute("stroke", colour)
.addAttribute("opacity", "0.3")
.addAttribute("fill", "none")
.addAttribute("style", "marker-end:url(#Arrow2Send);marker-mid:url(#Arrow2Send)")
.addAttribute("d", fmt.toString());
}
/**
* Draw a small arrow in the given direction with the given length.
*
* The direction is a bearing, ie is has to be projected to the xy coords we are using.
*
* @param co Draw from here
* @param dir Direction (bearing) of arrow.
* @param len Length in meters.
* @param colour Colour to draw it in.
* @param dashed
*/
private XY markDirection(Coord co, double dir, int len, String colour, boolean dashed) {
XY xy1 = toXY(co);
double lat1 = Utils.toRadians(co.getLatitude());
double lon1 = Utils.toRadians(co.getLongitude());
double dist = len / 6371000.0; // convert dist in meters to angular distance in radians
double brng = dir * (Math.PI / 180);
double lat2 = Math.asin( Math.sin(lat1)*Math.cos(dist) +
Math.cos(lat1)*Math.sin(dist)*Math.cos(brng) );
double lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(dist)*Math.cos(lat1),
Math.cos(dist)-Math.sin(lat1)*Math.sin(lat2));
lon2 = (lon2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..+180ยบ
Coord co2 = new Coord(lat2 / (Math.PI / 180), lon2 / (Math.PI / 180));
XY xy2 = toXY(co2);
String style = "marker-end:url(#Arrow2Send)";
if (dashed)
style += ";stroke-dasharray:2 2";
route.addElement("line")
.addAttribute("x1", xy1.getX())
.addAttribute("x2", xy2.getX())
.addAttribute("y1", xy1.getY())
.addAttribute("y2", xy2.getY())
.addAttribute("stroke", colour)
.addAttribute("stroke-width", "2")
.addAttribute("style", style);
return xy2;
}
private XY toXY(Coord coord) {
return doc.coord(coord.getLatitude(), coord.getLongitude());
}
/* === Main entry point === */
public void run(SvgDoc doc, String name) {
this.doc = doc;
this.route = doc.getRouting();
this.info = doc.getInfo();
this.roads = doc.getRoads();
this.spokes = doc.getSpokes();
this.setOutStream(System.err);
this.display(name, "NOD");
}
}