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");
}
}