[gnome-maps] GeoJSON/Layers: Formalize and add GUI



commit 37e8ac44dda132d598038f2db64140e97ead7558
Author: Hashem Nasarat <hashem riseup net>
Date:   Thu Dec 31 18:35:02 2015 -0500

    GeoJSON/Layers: Formalize and add GUI
    
    Maps supports displaying GeoJSON files as overlays on top of the map via
    drag and drop or by specifying the file on the command line.
    
    This commit adds the capability to select and load these files via the
    UI via a button in the Layers Popover; furthermore, the Layers Popover
    displays which overlays are loaded and allows the user to remove them at
    will.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=759544

 data/org.gnome.Maps.data.gresource.xml |    2 +
 data/ui/layers-popover.ui              |   33 ++++++++++--
 data/ui/shape-layer-file-chooser.ui    |   30 ++++++++++
 data/ui/shape-layer-row.ui             |   44 +++++++++++++++
 src/application.js                     |   18 ++----
 src/geoJSONShapeLayer.js               |   56 +++++++++++++++++++
 src/layersPopover.js                   |   94 ++++++++++++++++++++++++++++++--
 src/mainWindow.js                      |   16 +++---
 src/mapView.js                         |   82 +++++++++++++++++++---------
 src/org.gnome.Maps.src.gresource.xml   |    2 +
 src/shapeLayer.js                      |   85 +++++++++++++++++++++++++++++
 11 files changed, 404 insertions(+), 58 deletions(-)
---
diff --git a/data/org.gnome.Maps.data.gresource.xml b/data/org.gnome.Maps.data.gresource.xml
index 3e579f1..e1bad60 100644
--- a/data/org.gnome.Maps.data.gresource.xml
+++ b/data/org.gnome.Maps.data.gresource.xml
@@ -10,6 +10,8 @@
     <file preprocess="xml-stripblanks">ui/favorites-popover.ui</file>
     <file preprocess="xml-stripblanks">ui/instruction-row.ui</file>
     <file preprocess="xml-stripblanks">ui/layers-popover.ui</file>
+    <file preprocess="xml-stripblanks">ui/shape-layer-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/shape-layer-file-chooser.ui</file>
     <file preprocess="xml-stripblanks">ui/location-service-notification.ui</file>
     <file preprocess="xml-stripblanks">ui/main-window.ui</file>
     <file preprocess="xml-stripblanks">ui/map-bubble.ui</file>
diff --git a/data/ui/layers-popover.ui b/data/ui/layers-popover.ui
index c90f241..78a5a61 100644
--- a/data/ui/layers-popover.ui
+++ b/data/ui/layers-popover.ui
@@ -27,8 +27,6 @@
       <packing>
         <property name="left-attach">0</property>
         <property name="top-attach">0</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -51,8 +49,35 @@
       <packing>
         <property name="left-attach">0</property>
         <property name="top-attach">1</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkFrame" id="layers-list-box-frame">
+        <property name="visible">false</property>
+        <property name="shadow-type">in</property>
+        <child>
+          <object class="GtkListBox" id="layers-list-box">
+            <property name="name">layers-list-box</property>
+            <property name="visible">true</property>
+            <property name="can_focus">False</property>
+            <property name="selection-mode">none</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="top-attach">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="load-layer-button">
+        <property name="visible">True</property>
+        <property name="can-focus">True</property>
+        <property name="label" translatable="yes">Load Map Layer</property>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="top-attach">3</property>
       </packing>
     </child>
   </object>
diff --git a/data/ui/shape-layer-file-chooser.ui b/data/ui/shape-layer-file-chooser.ui
new file mode 100644
index 0000000..4654ab1
--- /dev/null
+++ b/data/ui/shape-layer-file-chooser.ui
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="Gjs_ShapeLayerFileChooser" parent="GtkFileChooserDialog">
+    <property name="title" translatable="yes">Open Layer</property>
+    <property name="select_multiple">True</property>
+    <child type="action">
+      <object class="GtkButton" id="openButton">
+        <property name="visible">True</property>
+        <property name="can-focus">True</property>
+        <property name="can-default">True</property>
+        <property name="use-underline">True</property>
+        <property name="label">_Open</property>
+        <style>
+          <class name="suggested-action"/>
+        </style>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="cancelButton">
+        <property name="visible">True</property>
+        <property name="use-underline">True</property>
+        <property name="label" translatable="yes">_Cancel</property>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="cancel">cancelButton</action-widget>
+      <action-widget response="ok" default="true">openButton</action-widget>
+    </action-widgets>
+  </template>
+</interface>
diff --git a/data/ui/shape-layer-row.ui b/data/ui/shape-layer-row.ui
new file mode 100644
index 0000000..55c380a
--- /dev/null
+++ b/data/ui/shape-layer-row.ui
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="Gjs_ShapeLayerRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkBox">
+        <property name="name">layers-row-box</property>
+        <property name="visible">True</property>
+        <property name="orientation">horizontal</property>
+        <property name="spacing">4</property>
+        <property name="margin-start">5</property>
+        <style>
+          <class name="horizontal"/>
+        </style>
+        <child>
+          <object class="GtkLabel" id="layerLabel">
+            <property name="visible">True</property>
+            <property name="hexpand">1</property>
+            <property name="ellipsize">end</property>
+            <property name="max-width-chars">1</property>
+            <property name="xalign">0</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="closeButton">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon_name">window-close-symbolic</property>
+                <property name="icon_size">1</property>
+              </object>
+            </child>
+            <style>
+              <class name="image-button"/>
+              <class name="flat"/>
+              <class name="circular"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/application.js b/src/application.js
index aed6c36..f5da10b 100644
--- a/src/application.js
+++ b/src/application.js
@@ -267,24 +267,18 @@ const Application = new Lang.Class({
         this._mainWindow.present();
     },
 
-    _openInternal: function(file) {
+    _openInternal: function(files) {
         if (!this._mainWindow || !this._mainWindow.mapView.view.realized)
             return;
 
-        let uri = file.get_uri();
+        let uri = files[0].get_uri();
 
         if (GLib.uri_parse_scheme(uri) === 'geo') {
             /* we get an uri that looks like geo:///lat,lon, remove slashes */
             let geoURI = uri.replace(/\//g, '');
             this._mainWindow.mapView.goToGeoURI(geoURI);
-            return;
-        }
-
-        let content_type = Gio.content_type_guess(uri, null)[0];
-        if (content_type === 'application/vnd.geo+json' ||
-            content_type === 'application/json') {
-            this._mainWindow.mapView.openGeoJSON(file);
-            return;
+        } else {
+            this._mainWindow.mapView.openShapeLayers(files);
         }
     },
 
@@ -294,10 +288,10 @@ const Application = new Lang.Class({
 
         let mapView = this._mainWindow.mapView;
         if (mapView.view.realized)
-            this._openInternal(files[0]);
+            this._openInternal(files);
         else
             mapView.view.connect('notify::realized',
-                                 this._openInternal.bind(this, files[0]));
+                                 this._openInternal.bind(this, files));
     },
 
     _onWindowDestroy: function(window) {
diff --git a/src/geoJSONShapeLayer.js b/src/geoJSONShapeLayer.js
new file mode 100644
index 0000000..d3049ef
--- /dev/null
+++ b/src/geoJSONShapeLayer.js
@@ -0,0 +1,56 @@
+/* -*- 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Hashem Nasarat <hashem riseup net>
+ */
+
+const Lang = imports.lang;
+
+const GeoJSONSource = imports.geoJSONSource;
+const ShapeLayer = imports.shapeLayer;
+
+const GeoJSONShapeLayer = new Lang.Class({
+    Name: 'GeoJSONShapeLayer',
+    Extends: ShapeLayer.ShapeLayer,
+
+    _init: function(params) {
+        this.parent(params);
+
+        this._mapSource = new GeoJSONSource.GeoJSONSource({
+            file: this.file,
+            mapView: this._mapView,
+            markerLayer: this._markerLayer
+        });
+    },
+
+    getName: function() {
+        /* Special Case since this file extension contains 2 periods */
+        let suffix = '.geo.json';
+        if (this.filename.endsWith(suffix))
+            return this.filename.replace(new RegExp(suffix + '$'), '');
+        else
+            return this.parent();
+    },
+
+    load: function() {
+        this._mapSource.parse();
+        this.parent();
+    }
+});
+
+GeoJSONShapeLayer.mimeTypes = ['application/vnd.geo+json',
+                               'application/json'];
+GeoJSONShapeLayer.displayName = 'GeoJSON';
diff --git a/src/layersPopover.js b/src/layersPopover.js
index 5913d8a..f07aa37 100644
--- a/src/layersPopover.js
+++ b/src/layersPopover.js
@@ -20,25 +20,107 @@
 const Gtk = imports.gi.Gtk;
 const Lang = imports.lang;
 
+const ShapeLayer = imports.shapeLayer;
 const Utils = imports.utils;
 
+const ShapeLayerRow = new Lang.Class({
+    Name: 'ShapeLayerRow',
+    Extends: Gtk.ListBoxRow,
+    Template: 'resource:///org/gnome/Maps/ui/shape-layer-row.ui',
+    Children: ['layerLabel', 'closeButton'],
+
+    _init: function(params) {
+        this.shapeLayer = params.shapeLayer;
+        delete params.shapeLayer;
+
+        this.parent(params);
+
+        this.layerLabel.label = this.shapeLayer.getName();
+        this.layerLabel.tooltip_text = this.shapeLayer.file.get_parse_name();
+    }
+});
+
+const ShapeLayerFileChooser = new Lang.Class({
+    Name: 'ShapeLayerFileChooser',
+    Extends: Gtk.FileChooserDialog,
+    Template: 'resource:///org/gnome/Maps/ui/shape-layer-file-chooser.ui',
+
+    _init: function(params) {
+        this.parent(params);
+
+        ShapeLayer.SUPPORTED_TYPES.forEach((function(layerClass) {
+            let filter = new Gtk.FileFilter();
+            layerClass.mimeTypes.forEach(filter.add_mime_type.bind(filter));
+            filter.set_name(layerClass.displayName);
+            this.add_filter(filter);
+        }).bind(this));
+    }
+});
+
 const LayersPopover = new Lang.Class({
     Name: 'LayersPopover',
     Extends: Gtk.Popover,
 
-    _init: function() {
-        let ui = Utils.getUIObject('layers-popover', [ 'grid',
-                                                       'street-layer-button',
-                                                       'aerial-layer-button' ]);
+    _init: function(params) {
+        this._mapView = params.mapView;
+        delete params.mapView;
+
+        this.ui = Utils.getUIObject('layers-popover',
+                                    [ 'grid',
+                                      'street-layer-button',
+                                      'aerial-layer-button',
+                                      'layers-list-box',
+                                      'layers-list-box-frame',
+                                      'load-layer-button' ]);
 
         this.parent({ width_request: 200,
                       no_show_all: true,
                       transitions_enabled: false,
                       visible: false });
 
-        ui.aerialLayerButton.join_group(ui.streetLayerButton);
+        this.ui.aerialLayerButton.join_group(this.ui.streetLayerButton);
 
         this.get_style_context().add_class('maps-popover');
-        this.add(ui.grid);
+        this.add(this.ui.grid);
+
+        this.ui.layersListBox.bind_model(this._mapView.shapeLayerStore,
+                                         this._listBoxCreateWidget.bind(this));
+        this.ui.layersListBox.connect('row-activated', (function(lb, row) {
+            this._mapView.gotoBBox(row.shapeLayer.bbox);
+        }).bind(this));
+
+        this.ui.layersListBox.set_header_func(function(row, before) {
+            let header = before ? new Gtk.Separator() : null;
+            row.set_header(header);
+        });
+
+        this.ui.loadLayerButton.connect('clicked',
+                                        this._onLoadLayerClicked.bind(this));
+    },
+
+    _onRemoveClicked: function(row, button) {
+        this._mapView.removeShapeLayer(row.shapeLayer);
+        if (this.ui.layersListBox.get_children().length <= 0)
+            this.ui.layersListBoxFrame.hide();
+    },
+
+    _onLoadLayerClicked: function(button) {
+        let fileChooser = new ShapeLayerFileChooser({
+            transient_for: this.get_parent(),
+        });
+
+        if (fileChooser.run() === Gtk.ResponseType.OK)
+            this._mapView.openShapeLayers(fileChooser.get_files());
+
+        fileChooser.destroy();
+        this.hide();
+    },
+
+    _listBoxCreateWidget: function(shapeLayer) {
+        let row = new ShapeLayerRow({ shapeLayer: shapeLayer });
+        row.closeButton.connect('clicked',
+                                this._onRemoveClicked.bind(this, row));
+        this.ui.layersListBoxFrame.show();
+        return row;
     }
 });
diff --git a/src/mainWindow.js b/src/mainWindow.js
index cd59837..681086e 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -88,7 +88,10 @@ const MainWindow = new Lang.Class({
 
         this._contextMenu = new ContextMenu.ContextMenu({ mapView: this._mapView });
 
-        this._layersButton.popover = new LayersPopover.LayersPopover();
+        this.layersPopover = new LayersPopover.LayersPopover({
+            mapView: this._mapView
+        });
+        this._layersButton.popover = this.layersPopover;
         this._favoritesButton.popover = new FavoritesPopover.FavoritesPopover({ mapView: this._mapView });
         this._overlay.add_overlay(new ZoomControl.ZoomControl(this._mapView));
 
@@ -155,16 +158,11 @@ const MainWindow = new Lang.Class({
         }).bind(this));
 
         this.connect('drag-data-received', (function(widget, ctx, x, y, data, info, time) {
-            let uri = data.get_uris()[0];
-            let content_type = Gio.content_type_guess(uri, null)[0];
-
-            if (content_type === 'application/vnd.geo+json' ||
-                content_type === 'application/json') {
-                this._mapView.openGeoJSON(Gio.file_new_for_uri(uri));
+            let files = data.get_uris().map(Gio.file_new_for_uri);
+            if (this._mapView.openShapeLayers(files))
                 Gtk.drag_finish(ctx, true, false, time);
-            } else {
+            else
                 Gtk.drag_finish(ctx, false, false, time);
-            }
         }).bind(this));
     },
 
diff --git a/src/mapView.js b/src/mapView.js
index 242ceb8..e15fbb5 100644
--- a/src/mapView.js
+++ b/src/mapView.js
@@ -23,6 +23,8 @@ const Champlain = imports.gi.Champlain;
 const Clutter = imports.gi.Clutter;
 const GObject = imports.gi.GObject;
 const Geocode = imports.gi.GeocodeGlib;
+const gettext = imports.gettext;
+const Gio = imports.gi.Gio;
 const GtkChamplain = imports.gi.GtkChamplain;
 const Lang = imports.lang;
 const Mainloop = imports.mainloop;
@@ -30,12 +32,13 @@ const Mainloop = imports.mainloop;
 const Application = imports.application;
 const ContactPlace = imports.contactPlace;
 const Geoclue = imports.geoclue;
-const GeoJSONSource = imports.geoJSONSource;
+const GeoJSONShapeLayer = imports.geoJSONShapeLayer;
 const Location = imports.location;
 const Maps = imports.gi.GnomeMaps;
 const MapWalker = imports.mapWalker;
 const Place = imports.place;
 const PlaceMarker = imports.placeMarker;
+const ShapeLayer = imports.shapeLayer;
 const StoredRoute = imports.storedRoute;
 const TurnPointMarker = imports.turnPointMarker;
 const UserLocationMarker = imports.userLocationMarker;
@@ -87,6 +90,8 @@ const MapView = new Lang.Class({
         this._factory = Champlain.MapSourceFactory.dup_default();
         this.setMapType(mapType);
 
+        this.shapeLayerStore = new Gio.ListStore(GObject.TYPE_OBJECT);
+
         this._updateUserLocation();
         Application.geoclue.connect('location-changed',
                                     this._updateUserLocation.bind(this));
@@ -140,6 +145,8 @@ const MapView = new Lang.Class({
 
         this._annotationMarkerLayer = new Champlain.MarkerLayer({ selection_mode: mode });
         this.view.add_layer(this._annotationMarkerLayer);
+
+        ShapeLayer.SUPPORTED_TYPES.push(GeoJSONShapeLayer.GeoJSONShapeLayer);
     },
 
     _connectRouteSignals: function() {
@@ -187,33 +194,49 @@ const MapView = new Lang.Class({
             }
         }
 
-        overlay_sources.forEach((function(overlay) {
-            this.view.add_overlay_source(overlay, 255);
+        overlay_sources.forEach((function(source) {
+            this.view.add_overlay_source(source, 255);
         }).bind(this));
     },
 
-    openGeoJSON: function(file) {
-        try {
-            this._annotationMarkerLayer.remove_all();
-            let geoJSONSource = new GeoJSONSource.GeoJSONSource({
-                file: file,
-                mapView: this,
-                markerLayer: this._annotationMarkerLayer
-            });
-            geoJSONSource.parse();
+    openShapeLayers: function(files) {
+        let bbox = new Champlain.BoundingBox();
+        let ret = true;
+        files.forEach((function(file){
+            try {
+                let i = this._findShapeLayerIndex(file);
+                let layer = (i > -1) ? this.shapeLayerStore.get_item(i) : null;
+                if (!layer) {
+                    layer = ShapeLayer.newFromFile(file, this);
+                    if (!layer)
+                        throw new Error(_("File type is not supported"));
+                    layer.load();
+                    this.shapeLayerStore.append(layer);
+                }
+                bbox.compose(layer.bbox);
+            } catch (e) {
+                Utils.debug(e);
+                let msg = _("Failed to open layer");
+                Application.notificationManager.showMessage(msg);
+                ret = false;
+            }
+        }).bind(this));
+
+        this.gotoBBox(bbox);
+        return ret;
+    },
 
-            if (this._annotationSource)
-                this.view.remove_overlay_source(this._annotationSource);
+    removeShapeLayer: function(shapeLayer) {
+        shapeLayer.unload();
+        let i = this._findShapeLayerIndex(shapeLayer.file);
+        this.shapeLayerStore.remove(i);
+    },
 
-            this._annotationSource = geoJSONSource;
-            this.view.add_overlay_source(this._annotationSource, 255);
-            if (geoJSONSource.bbox.is_valid())
-                this._gotoBBox(geoJSONSource.bbox);
-        } catch(e) {
-            let msg = _("Failed to parse GeoJSON file");
-            Application.notificationManager.showMessage(msg);
-            Utils.debug("failed to parse geojson file: %s".format(e.message));
-        }
+    _findShapeLayerIndex: function(file) {
+        for (let i = 0; i < this.shapeLayerStore.get_n_items(); i++)
+            if (this.shapeLayerStore.get_item(i).file.equal(file))
+                return i;
+        return -1;
     },
 
     goToGeoURI: function(uri) {
@@ -291,10 +314,15 @@ const MapView = new Lang.Class({
                                                        bottom: box[1],
                                                        left: box[2],
                                                        right: box[3] });
-        this._gotoBBox(bounding_box, true);
+        this.gotoBBox(bounding_box, true);
     },
 
-    _gotoBBox: function(bbox, linear) {
+    gotoBBox: function(bbox, linear) {
+        if (!bbox.is_valid()) {
+            Utils.debug('Bounding box is invalid');
+            return;
+        }
+
         let [lat, lon] = bbox.get_center();
         let place = new Place.Place({
             location: new Location.Location({ latitude  : lat,
@@ -335,7 +363,7 @@ const MapView = new Lang.Class({
         }).bind(this));
 
         if (places.length > 1)
-            this._gotoBBox(contact.bounding_box);
+            this.gotoBBox(contact.bounding_box);
         else
             new MapWalker.MapWalker(places[0], this).goTo(true);
     },
@@ -390,7 +418,7 @@ const MapView = new Lang.Class({
         route.path.forEach(this._routeLayer.add_node.bind(this._routeLayer));
 
         this._showDestinationTurnpoints();
-        this._gotoBBox(route.bbox);
+        this.gotoBBox(route.bbox);
     },
 
     _showDestinationTurnpoints: function() {
diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index 66302ac..4f7fd3a 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -12,6 +12,8 @@
     <file>exportViewDialog.js</file>
     <file>facebookBackend.js</file>
     <file>favoritesPopover.js</file>
+    <file>geoJSONShapeLayer.js</file>
+    <file>shapeLayer.js</file>
     <file>foursquareBackend.js</file>
     <file>foursquareGoaAuthorizer.js</file>
     <file>geoclue.js</file>
diff --git a/src/shapeLayer.js b/src/shapeLayer.js
new file mode 100644
index 0000000..7bd511d
--- /dev/null
+++ b/src/shapeLayer.js
@@ -0,0 +1,85 @@
+/* -*- 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Hashem Nasarat <hashem riseup net>
+ */
+
+const Champlain = imports.gi.Champlain;
+const Gio = imports.gi.Gio;
+const GObject = imports.gi.GObject;
+const Lang = imports.lang;
+
+const SUPPORTED_TYPES = [];
+
+function newFromFile(file, mapView) {
+    let contentType = Gio.content_type_guess(file.get_uri(), null)[0];
+    for (let i in SUPPORTED_TYPES) {
+        let layerClass = SUPPORTED_TYPES[i];
+        if (layerClass.mimeTypes.indexOf(contentType) > -1) {
+            return new layerClass({ file: file, mapView: mapView });
+        }
+    }
+    return null;
+}
+
+const ShapeLayer = new Lang.Class({
+    Name: 'ShapeLayer',
+    Extends: GObject.Object,
+    Abstract: true,
+
+    _init: function(params) {
+        this.parent();
+
+        this._mapView = params.mapView;
+        this.file = params.file;
+
+        this.filename = this.file.query_info(
+            Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+            Gio.FileQueryInfoFlags.NONE,
+            null
+        ).get_attribute_string(Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
+
+        this._markerLayer = new Champlain.MarkerLayer({
+            selection_mode: Champlain.SelectionMode.SINGLE
+        });
+        this._mapSource = null;
+    },
+
+    get bbox() {
+        return this._mapSource.bbox;
+    },
+
+    getName: function() {
+        /*
+         * Remove file extension and use that in lieu of a fileformat-specific
+         * display name.
+         */
+        return this.filename.replace(/\.[^\.]+$/, '');
+    },
+
+    load: function() {
+        this._mapView.view.add_layer(this._markerLayer);
+        this._mapView.view.add_overlay_source(this._mapSource, 255);
+    },
+
+    unload: function() {
+        this._mapView.view.remove_layer(this._markerLayer);
+        this._mapView.view.remove_overlay_source(this._mapSource);
+    }
+});
+
+ShapeLayer.mimeTypes = [];
+ShapeLayer.displayName = '';


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