[gnome-maps/wip/mlundblad/transit-routing: 22/23] Add print layout for transit



commit 6533b3409628ea08b7bc663cbe52c42f6a673d77
Author: Marcus Lundblad <ml update uu se>
Date:   Tue Sep 20 23:27:09 2016 +0200

    Add print layout for transit
    
    Adds a printing layout implementation for transit itineraries.
    It will print the instructions for transit switches during a
    trip. Also includes minimaps for walking sections.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=755808

 src/org.gnome.Maps.src.gresource.xml |    1 +
 src/transitPrintLayout.js            |  285 ++++++++++++++++++++++++++++++++++
 2 files changed, 286 insertions(+), 0 deletions(-)
---
diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index 698a8b8..df24658 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -85,6 +85,7 @@
     <file>transitMoreRow.js</file>
     <file>transitOptions.js</file>
     <file>transitPlan.js</file>
+    <file>transitPrintLayout.js</file>
     <file>transitRouteLabel.js</file>
     <file>transitStopRow.js</file>
     <file>transitWalkMarker.js</file>
diff --git a/src/transitPrintLayout.js b/src/transitPrintLayout.js
new file mode 100644
index 0000000..41d5dd5
--- /dev/null
+++ b/src/transitPrintLayout.js
@@ -0,0 +1,285 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2017 Marcus Lundblad.
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+const Lang = imports.lang;
+
+const Cairo = imports.cairo;
+const Champlain = imports.gi.Champlain;
+const Clutter = imports.gi.Clutter;
+const Gdk = imports.gi.Gdk;
+const Gtk = imports.gi.Gtk;
+
+const MapSource = imports.mapSource;
+const PrintLayout = imports.printLayout;
+const TransitArrivalMarker = imports.transitArrivalMarker;
+const TransitArrivalRow = imports.transitArrivalRow;
+const TransitBoardMarker = imports.transitBoardMarker;
+const TransitLegRow = imports.transitLegRow;
+const TransitWalkMarker = imports.transitWalkMarker;
+
+// stroke color for walking paths
+const _STROKE_COLOR = new Clutter.Color({ red: 0,
+                                          blue: 0,
+                                          green: 0,
+                                          alpha: 255 });
+const _STROKE_WIDTH = 5.0;
+
+// All following constants are ratios of surface size to page size
+const _Header = {
+    SCALE_X: 0.9,
+    SCALE_Y: 0.03,
+    SCALE_MARGIN: 0.01
+};
+const _MapView = {
+    SCALE_X: 1.0,
+    SCALE_Y: 0.4,
+    SCALE_MARGIN: 0.04,
+    ZOOM_LEVEL: 18
+};
+const _Instruction = {
+    SCALE_X: 0.9,
+    SCALE_Y: 0.1,
+    SCALE_MARGIN: 0.01
+};
+
+const TransitPrintLayout = new Lang.Class({
+    Name: 'TransitPrintLayout',
+    Extends: PrintLayout.PrintLayout,
+
+    _init: function(params) {
+        this._itinerary = params.itinerary;
+        delete params.itinerary;
+
+        params.totalSurfaces = this._getNumberOfSurfaces();
+
+        this.parent(params);
+    },
+
+    _getNumberOfSurfaces: function() {
+        // always one fixed surface for the title label
+        let numSurfaces = 1;
+
+        for (let i = 0; i < this._itinerary.legs.length; i++) {
+            numSurfaces++;
+            // add a surface when a leg of the itinerary should have a map view
+            if (this._legHasMiniMap(i))
+                numSurfaces++;
+        }
+
+        // always include the arrival row
+        numSurfaces++;
+
+        return numSurfaces;
+    },
+
+    _drawMapView: function(width, height, zoomLevel, index) {
+        let pageNum = this.numPages - 1;
+        let x = this._cursorX;
+        let y = this._cursorY;
+        let mapSource = MapSource.createPrintSource();
+        let markerLayer = new Champlain.MarkerLayer();
+        let view = new Champlain.View({ width: width,
+                                        height: height,
+                                        zoom_level: zoomLevel });
+        let leg = this._itinerary.legs[index];
+        let nextLeg = this._itinerary.legs[index + 1];
+        let previousLeg = index === 0 ? null : this._itinerary.legs[index - 1];
+
+        view.set_map_source(mapSource);
+        /* we want to add the path layer before the marker layer, so that
+         * boarding marker are drawn about the walk dash lines
+         */
+        this._addRouteLayer(view, index);
+        view.add_layer(markerLayer);
+
+        markerLayer.add_marker(this._createStartMarker(leg, previousLeg));
+        if (nextLeg)
+            markerLayer.add_marker(this._createBoardMarker(nextLeg));
+        else
+            markerLayer.add_marker(this._createArrivalMarker(leg));
+
+        /* in some cases, we seem to get get zero distance walking instructions
+         * within station complexes, don't try to show a bounding box for low
+         * distances, instead center on the spot
+         */
+        if (leg.distance < 10)
+            view.center_on(leg.fromCoordinate[0], leg.fromCoordinate[1]);
+        else
+            view.ensure_visible(leg.bbox, false);
+        if (view.state !== Champlain.State.DONE) {
+            let notifyId = view.connect('notify::state', (function() {
+                if (view.state === Champlain.State.DONE) {
+                    view.disconnect(notifyId);
+                    let surface = view.to_surface(true);
+                    if (surface)
+                        this._addSurface(surface, x, y, pageNum);
+                }
+            }).bind(this));
+        } else {
+            let surface = view.to_surface(true);
+            if (surface)
+                this._addSurface(surface, x, y, pageNum);
+        }
+    },
+
+    _createStartMarker: function(leg, previousLeg) {
+        return new TransitWalkMarker.TransitWalkMarker({ leg: leg,
+                                                         previousLeg: previousLeg });
+    },
+
+    _createBoardMarker: function(leg) {
+        return new TransitBoardMarker.TransitBoardMarker({ leg: leg });
+    },
+
+    _createArrivalMarker: function(leg) {
+        return new TransitArrivalMarker.TransitArrivalMarker({ leg: leg });
+    },
+
+    _addRouteLayer: function(view, index) {
+        let routeLayer = new Champlain.PathLayer({ stroke_width: _STROKE_WIDTH,
+                                                   stroke_color: _STROKE_COLOR });
+        let leg = this._itinerary.legs[index];
+
+        routeLayer.set_dash([5, 5]);
+        view.add_layer(routeLayer);
+
+        /* if this is a walking leg and not at the start, "stitch" it
+         * together with the end point of the previous leg, as the walk
+         * route might not reach all the way
+         */
+        if (index > 0 && !leg.transit) {
+            let previousLeg = this._itinerary.legs[index - 1];
+            let lastPoint =
+                previousLeg.polyline[previousLeg.polyline.length - 1];
+
+            routeLayer.add_node(lastPoint);
+        }
+
+        leg.polyline.forEach(routeLayer.add_node.bind(routeLayer));
+
+        /* like above, "stitch" the route segment with the next one if it's
+         * a walking leg, and not the last one
+         */
+        if (index < this._itinerary.legs.length - 1 && !leg.transit) {
+            let nextLeg = this._itinerary.legs[index + 1];
+            let firstPoint = nextLeg.polyline[0];
+
+            routeLayer.add_node(firstPoint);
+        }
+    },
+
+    _renderWidget: function(widget, width, height) {
+        let pageNum = this.numPages - 1;
+        let x = this._cursorX;
+        let y = this._cursorY;
+
+        let offscreenWindow = new Gtk.OffscreenWindow({ visible: true });
+
+        widget.width_request = width;
+        widget.height_request = height;
+
+        // Paint the background of the row to be transparent
+        widget.connect('draw', (function(widget, cr) {
+            cr.setSourceRGBA(0.0, 0.0, 0.0, 0.0);
+            cr.setOperator(Cairo.Operator.SOURCE);
+            cr.paint();
+            cr.setOperator(Cairo.Operator.OVER);
+        }).bind(this));
+
+        widget.queue_draw();
+        offscreenWindow.add(widget);
+        offscreenWindow.set_valign(Gtk.Align.START);
+        offscreenWindow.connect('damage-event', (function (widget) {
+            let surface = widget.get_surface();
+            this._addSurface(surface, x, y, pageNum);
+        }).bind(this));
+    },
+
+    _drawInstruction: function(width, height, leg, start) {
+        let legRow = new TransitLegRow.TransitLegRow({
+            visible: true,
+            leg: leg,
+            start: start,
+            print: true
+        });
+
+        this._renderWidget(legRow, width, height);
+    },
+
+    _drawArrival: function(width, height) {
+        let arrivalRow = new TransitArrivalRow.TransitArrivalRow({
+            visible: true,
+            itinerary: this._itinerary,
+            print: true
+        });
+
+        this._renderWidget(arrivalRow, width, height);
+    },
+
+    _legHasMiniMap: function(index) {
+        let leg = this._itinerary.legs[index];
+        return !leg.transit;
+    },
+
+    render: function() {
+        let headerWidth = _Header.SCALE_X * this._pageWidth;
+        let headerHeight = _Header.SCALE_Y * this._pageHeight;
+        let headerMargin = _Header.SCALE_MARGIN * this._pageHeight;
+
+        let mapViewWidth = _MapView.SCALE_X * this._pageWidth;
+        let mapViewHeight = _MapView.SCALE_Y * this._pageHeight;
+        let mapViewMargin = _MapView.SCALE_MARGIN * this._pageHeight;
+        let mapViewZoomLevel = _MapView.ZOOM_LEVEL;
+
+        let instructionWidth = _Instruction.SCALE_X * this._pageWidth;
+        let instructionHeight = _Instruction.SCALE_Y * this._pageHeight;
+        let instructionMargin = _Instruction.SCALE_MARGIN * this._pageHeight;
+
+        let dy = headerHeight + headerMargin;
+
+        this._createNewPage();
+        this._adjustPage(dy);
+        this._drawHeader(headerWidth, headerHeight);
+        this._cursorY += dy;
+
+        for (let i = 0; i < this._itinerary.legs.length; i++) {
+            let leg = this._itinerary.legs[i];
+            let hasMap = this._legHasMiniMap(i);
+            let instructionDy = instructionHeight + instructionMargin;
+            let mapDy = hasMap ? mapViewHeight + mapViewMargin : 0;
+
+            dy = instructionDy + mapDy;
+            this._adjustPage(dy);
+            this._drawInstruction(instructionWidth, instructionHeight, leg,
+                                  i === 0);
+            this._cursorY += instructionDy;
+
+            if (hasMap) {
+                let nextLeg = i < this._itinerary.legs.length - 1 ?
+                              this._itinerary.legs[i + 1] : null;
+                this._drawMapView(mapViewWidth, mapViewHeight, mapViewZoomLevel, i);
+                this._cursorY += mapDy;
+            }
+        }
+
+        this._drawArrival(instructionWidth, instructionHeight);
+    }
+});


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]