[gnome-maps/wip/jonasdn/geojson: 51/55] Add GeoJSONSource



commit eb02d824e33b693e364d341a62de2dd3bc390510
Author: Jonas Danielsson <jonas threetimestwo org>
Date:   Fri Oct 23 12:02:54 2015 +0200

    Add GeoJSONSource
    
    Add a Champlain::TileSource that creates tiles from a GeoJSON file.
    This is based on the geojson-vt library from Mapbox.

 src/geoJSONSource.js                 |  239 ++++++++++++++++++++++++++++++++++
 src/org.gnome.Maps.src.gresource.xml |    1 +
 2 files changed, 240 insertions(+), 0 deletions(-)
---
diff --git a/src/geoJSONSource.js b/src/geoJSONSource.js
new file mode 100644
index 0000000..b2878e7
--- /dev/null
+++ b/src/geoJSONSource.js
@@ -0,0 +1,239 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Jonas Danielsson <jonas threetimestwo org>
+ */
+
+const Cairo = imports.cairo;
+const Champlain = imports.gi.Champlain;
+const Clutter = imports.gi.Clutter;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+
+const geojsonvt = imports.index.geojsonvt;
+const Location = imports.location;
+const Place = imports.place;
+const PlaceMarker = imports.placeMarker;
+const Utils = imports.utils;
+
+const TileFeature = { POINT: 1,
+                      LINESTRING: 2,
+                      POLYGON: 3 };
+
+const GeoJSONSource = new Lang.Class({
+    Name: 'GeoJSONSource',
+    Extends: Champlain.TileSource,
+
+    _init: function(params) {
+        this.parent();
+
+        this._file = params.file;
+        this._mapView = params.mapView;
+        this._markerLayer = params.markerLayer;
+        this._bbox = new Champlain.BoundingBox();
+    },
+
+    get bbox() {
+        return this._bbox;
+    },
+
+    vfunc_get_tile_size: function() {
+        return 256;
+    },
+
+    vfunc_get_max_zoom_level: function() {
+        return 20;
+    },
+
+    vfunc_get_min_zoom_level: function() {
+        return 0;
+    },
+
+    vfunc_get_id: function() {
+        return 'geoJSONSource';
+    },
+
+    vfunc_get_name: function() {
+        return 'geoJSONSource';
+    },
+
+    vfunc_fill_tile: function(tile) {
+        if (tile.get_state() === Champlain.State.DONE)
+            return;
+
+        tile.connect('render-complete', (function(tile, data, size, error) {
+            if(!error) {
+                tile.set_state(Champlain.State.DONE);
+                tile.display_content();
+            }
+            else if(this.next_source)
+                this.next_source.fill_tile(tile);
+        }).bind(this));
+
+
+        Mainloop.idle_add(this._renderTile.bind(this, tile));
+    },
+
+    _isValid: function(coordinate) {
+        if (coordinate[0] > 180 || coordinate[0] < -180)
+            return false;
+
+        if (coordinate[1] > 90 || coordinate[1] < -90)
+            return false;
+
+        return true;
+    },
+
+    _compose: function(coordinates) {
+        for (let i = 0; i < coordinates.length; i++) {
+            if (this._isValid(coordinates[i]))
+                this._bbox.extend(coordinates[i][1], coordinates[i][0]);
+            else
+                return false;
+        }
+
+        return true;
+    },
+
+    _validateLineString: function(coordinates) {
+        return this._compose(coordinates);
+    },
+
+    _validatePolygon: function(coordinates) {
+        for (let i = 0; i < coordinates.length; i++)
+            if (!this._compose(coordinates[i]))
+                return false;
+
+        return true;
+    },
+
+    _validateFeature: function(feature) {
+        let geometry = feature.geometry;
+        if (!geometry)
+            return false;
+
+        if (geometry.type === "LineString") {
+            return this._validateLineString(geometry.coordinates);
+        } else if (geometry.type === "MultiLineString") {
+            for (let i = 0; i < geometry.coordinates.length; i++)
+                if (!this._validateLineString(geometry.coordinates[i]))
+                    return false;
+            return true;
+        } else if (geometry.type === "Polygon") {
+            return this._validatePolygon(geometry.coordinates);
+        } else if (geometry.type === "MultiPolygon") {
+            for (let i = 0; i < geometry.coordinates.length; i++)
+                if (!this._validatePolygon(geometry.coordinates[i]))
+                    return false;
+            return true;
+        } else if (geometry.type === "Point") {
+            let name = null;
+            if (feature.properties)
+                name = feature.properties.name;
+
+            let location = new Location.Location({
+                latitude: geometry.coordinates[1],
+                longitude: geometry.coordinates[0]
+            });
+
+            let place = new Place.Place({ name: name,
+                                          location: location });
+            let placeMarker = new PlaceMarker.PlaceMarker({ place: place,
+                                                            mapView: this._mapView });
+            this._markerLayer.add_marker(placeMarker);
+            return true;
+        }
+
+        return false;
+    },
+
+    _validateInternal: function(root) {
+        if (!root || !root.type)
+            return false;
+
+        if (root.type === "FeatureCollection") {
+            for (let i = 0; i < root.features.length; i++)
+                if (!this._validateFeature(root.features[i]))
+                    return false;
+        } else if (root.type === "Feature")
+            return this._validateFeature(root);
+
+        return true;
+    },
+
+    validate: function() {
+        let [status, buffer] = this._file.load_contents(null);
+        if (!status)
+            throw new Error(_("Failed to load file"));
+
+        let json = JSON.parse(buffer);
+        status = this._validateInternal(json);
+        if (!status)
+            throw new Error(_("Failed to validate GeoJSON"));
+
+        this._tileIndex = geojsonvt(json, { extent: 256,
+                                            maxZoom: 20 });
+    },
+
+    _renderTile: function(tile) {
+        let tileJSON = this._tileIndex.getTile(tile.zoom_level, tile.x, tile.y);
+
+        if (!tileJSON) {
+            tile.emit('render-complete', null, 0, false);
+            return;
+        }
+
+        let content = new Clutter.Canvas({ width: 256,
+                                           height: 256 });
+        tile.content = new Clutter.Actor({ width: 256,
+                                           height: 256,
+                                           content: content });
+
+        content.connect('draw', (function(canvas, cr) {
+            cr.setOperator(Cairo.Operator.CLEAR);
+            cr.paint();
+            cr.setOperator(Cairo.Operator.OVER);
+            cr.setSourceRGB(0, 0, 0);
+            cr.setLineWidth(1);
+
+            tileJSON.features.forEach(function(feature) {
+                if (feature.type === TileFeature.POINT)
+                    return;
+
+                feature.geometry.forEach(function(geometry) {
+                    let first = true;
+                    cr.moveTo(0, 0);
+                    geometry.forEach(function(coord) {
+                        if (first) {
+                            cr.moveTo(coord[0], coord[1]);
+                            first = false;
+                        } else {
+                            cr.lineTo(coord[0], coord[1]);
+                        }
+                    });
+                });
+                if (feature.type === TileFeature.POLYGON)
+                    cr.closePath();
+                cr.stroke();
+            });
+
+            tile.emit('render-complete', null, 0, false);
+        }).bind(this));
+
+        content.invalidate();
+    }
+});
diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index e75f3fa..f7941ec 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -15,6 +15,7 @@
     <file>foursquareGoaAuthorizer.js</file>
     <file>geoclue.js</file>
     <file>geocodeService.js</file>
+    <file>geoJSONSource.js</file>
     <file>http.js</file>
     <file>layersPopover.js</file>
     <file>location.js</file>


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