Subversion Repositories display

Rev

Rev 426 | 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.nod;

import java.util.Formatter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.net.NODHeader;

import org.dom4j.Element;
import test.display.CommonDisplay;
import test.elements.Line;
import test.files.Nod2Record;
import test.files.RoadData;
import test.files.RouteArc;
import test.files.RouteCenter;
import test.files.RouteNode;
import test.files.Segment;
import test.svg.SvgDoc;
import test.svg.XY;

/**
 * Convert a nod file to svg to show the routing relationships.
 *
 * @author Steve Ratcliffe
 */

public class Route extends CommonDisplay {

        private SvgDoc doc;

        // We can limit by route center, or nodes in which case the route centre(s) for the nodes will be selected.
        private Set<Integer> nodes;
        private Set<Integer> centers = new HashSet<>();

        // An area filter restricts only nodes within the given area to be displayed, along with every associated
        // road.
        private Area area;

        private Element route;
        private Element info;
    private Element roads;
    private Element spokes;
    private Element marked;

    private final Set<Integer> roadIds = new HashSet<>();


        protected void print() {

        openLbl();
        openTre();
        openNet();

        openRgn();

        reader.position(0);
        openNod();
        List<RouteCenter> routeCenters = nod.readNodeCenters();

        initRoads();

        int pos = 0;
        int end = ((NODHeader) nod.getHeader()).getRoadSection().getSize();
        do {
            Nod2Record nod2 = nod.getNod2(pos);

            RouteNode node = nod2.getNode();
            if (nodes.contains(node.getOffset()))
            {
                int offset = nod2.getRoadData().getOffset();
                System.out.printf("adding road %x\n", offset);
                roadIds.add(offset);
            }

            pos = (int) nod2.getNext();
        } while (pos < end);

        // If we have nodes but no area, then get the set of centers that will cover them.
            if (!nodes.isEmpty() && area == null) {
                    for (int n : nodes) {
                            RouteNode node = nod.getNode(n);
                            centers.add(node.getCenter().getIndex());

                if (node.getLinkedRoad() != null) {
                    System.out.printf("n:%x adding road %x\n", node.getOffset(), node.getLinkedRoad().getOffset());
                    roadIds.add(node.getLinkedRoad().getOffset());
                }
            }
            }

            // If we got no centers, then use the first few, unless we have an area filter when all will be included
                // on the basis that the chosen area will be small enough.
                int limit = (area == null)? 4: routeCenters.size();
                limit = Math.min(routeCenters.size(), limit);
                if (centers.isEmpty()) {
                        for (int i = 0; i < limit; i++)
                            centers.add(i);
            }

            // Now draw each of the wanted centers.
        for (int i : centers) {
            RouteCenter c = routeCenters.get(i);
            displayCenter(c);
        }

        drawRoads();
    }

    private void displayCenter(RouteCenter c) {
        XY xy1 = toXY(c.getCoord());

        route.addElement("circle")
                .addAttribute("cx", xy1.getX())
                .addAttribute("cy", xy1.getY())
                .addAttribute("r", "10")
                .addAttribute("fill", "none")
                .addAttribute("stroke", "orange")
                .addAttribute("stroke-width", "3");

            route.addElement("text")
                            .addAttribute("x", xy1.getX(-7))
                            .addAttribute("y", xy1.getY(+5))
                            .setText(String.valueOf(c.getIndex()));

            for (RouteNode node : c.nodes().values()) {
                    if (node.isBad())
                            continue;

                        if (area != null && !area.contains(node.getCoord()))
                                continue;

            drawRouteNode(node);

            if (node.hasArcs()) {
                for (RouteArc arc : node.getArcs()) {
                    if (arc.isBad())
                        continue;

                    drawArc(arc);
                }
            }
        }
    }

        private void drawRouteNode(RouteNode node) {
                boolean markedNode = nodes.contains(node.getOffset());

                XY xy = toXY(node.getCoord());
                Element circ = (markedNode? marked: route).addElement("circle")
                                .addAttribute("cx", xy.getX())
                                .addAttribute("cy", xy.getY())
                                .addAttribute("r", "3")
                                ;

                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()));

                Element rect = g.addElement("rect")
                                .addAttribute("width", "50")
                                .addAttribute("height", "10")
                                .addAttribute("fill", "white")
                                .addAttribute("opacity", "0.7")
                                .addAttribute("stroke-width", "0.5")
                                .addAttribute("stroke", "black");

                if (markedNode)
                        rect.addAttribute("fill", "#ff0081");

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

            Element layer = (nodes.contains(arc.getNode().getOffset()))? marked: route;

        RouteNode n1 = arc.getNode();
        RouteNode n2 = nod.getNode(arc.getLink());

        if (n2 != null) {
                assert arc.hasDirection();

                        // Draw an arrow third way to the target node.
            XY p1 = toXY(n1.getCoord());
            XY p2 = toXY(n2.getCoord());

                        int x2 = p1.getXi() + (p2.getXi() - p1.getXi())/3;
                        int y2 = p1.getYi() + (p2.getYi() - p1.getYi())/3;

                String style = "marker-end:url(#Arrow2Send)";
                if (arc.hasCurve())
                        style += ";stroke-dasharray:2 2";

                layer.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", style)
            ;

                // Add info on the arc.
                Element g = layer.addElement("g")
                                .addAttribute("transform",
                                                String.format("translate(%d,%d)", x2, y2));

                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(), nod.rawToMeters(arc.getDistance()), arc.getInitialDegrees(),
                                                arc.getDirection()));

                // Add the rest of the arrow.
                layer.addElement("line")
                                .addAttribute("x1", String.valueOf(x2))
                                .addAttribute("y1", String.valueOf(y2))
                                .addAttribute("x2", p2.getX())
                                .addAttribute("y2", p2.getY())
                                .addAttribute("stroke", colour)
                                .addAttribute("stroke-wdith", "1")
                                .addAttribute("style", style);

                markDirection(layer, n1.getCoord(), arc.getInitialDegrees(), 50, colour, false);
        }
    }

        private void drawRoads() {
                for (Integer i : roadIds) {

                        RoadData road = net.getRoad(i);
                        int part = 0;
                        List<Segment> segments = road.getSegments();
                        int nseg = segments.size();
                        for (Segment seg : segments) {
                                Line line = seg.getLine();
                                Coord first = line.getFirstPoint();
                                XY xy1 = toXY(first);
                                Coord next = line.getCoords().get(1);
                                XY p = xy1.offset(toXY(next), 2);
                                roads.addElement("text")
                                                .addAttribute("x", p.getX())
                                                .addAttribute("y", p.getY())
                                                .addAttribute("font-size", "3px")
                                                .setText(String.format("Road:%x %d of %d c/s=%d/%d t=%d", road.getOffset(),
                                                                part+1, nseg, road.getNod2().getRoadClass(), road.getNod2().getSpeed(),
                                                                line.getType()));

                                drawLine(road, line, part == nseg-1);
                                part++;
                        }
                }
        }

    private void drawLine(RoadData road, Line line, boolean last) {
        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 (road.getNod2().getRoadClass()) {
            case 1: colour = "yellow"; break;
            case 2: colour = "red"; break;
            case 3: colour = "green"; break;
            case 4: colour = "blue"; break;
            default: colour = "#775599"; break;
        }
        roads.addElement("path")
                .addAttribute("stroke", colour)
                .addAttribute("fill", "none")
                .addAttribute("stroke-width", "2")
                                .addAttribute("style",
                                                last ? "marker-end:url(#Arrow2Send)" : "marker-end:url(#DiamondSend)")
                                .addAttribute("d", fmt.toString());

            for (Coord co : line.getCoords()) {
                    if (co.getId() > 0) {
                            XY point = toXY(co);
                            roads.addElement("circle")
                                            .addAttribute("cx", point.getX())
                                            .addAttribute("cy", point.getY())
                                            .addAttribute("fill", colour)
                                            .addAttribute("r", "2");
                    }
            }
    }

        /**
         * 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 layer
         * @param co Draw from here
         * @param dir Direction (bearing) of arrow in degrees.
         * @param len Length in meters.
         * @param colour Colour to draw it in.
         * @param dashed
         */

    private Coord markDirection(Element layer, 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(#DotM)";
                if (dashed)
                        style += ";stroke-dasharray:2 2";
                layer.addElement("line")
                                .addAttribute("x1", xy1.getX())
                                .addAttribute("x2", xy2.getX())
                                .addAttribute("y1", xy1.getY())
                                .addAttribute("y2", xy2.getY())
                                .addAttribute("stroke", colour)
                                .addAttribute("stroke-width", "0.5")
                                .addAttribute("style", style);
                return co2;
        }

        private XY toXY(Coord coord) {
                return doc.coord(coord.getLatitude(), coord.getLongitude());
        }

    /* === Main entry point === */
        public void run(SvgRouteDoc doc, String name) {
                this.doc = doc;

                this.route = doc.getRouting();
                this.info = doc.getInfo();
        this.roads = doc.getRoads();
        this.spokes = doc.getSpokes();
        this.marked = doc.getMarked();

                this.setOutStream(System.err);
                this.display(name, "NOD");
        }

        public void setArea(Area area) {
                this.area = area;
        }

        public void setNodes(Set<Integer> nodes) {
                this.nodes = nodes;
        }

        public Set<Integer> getNodes() {
                return nodes;
        }

        public void setCenters(Set<Integer> centers) {
                this.centers = centers;
        }
}