diff options
| -rw-r--r-- | CHANGES.md | 1 | ||||
| -rw-r--r-- | ishtar_common/forms_common.py | 5 | ||||
| -rw-r--r-- | ishtar_common/static/gis/js/OLMapWidget.js | 246 | ||||
| -rw-r--r-- | ishtar_common/templates/gis/openlayers-osm.html | 59 | ||||
| -rw-r--r-- | ishtar_common/widgets.py | 22 | 
5 files changed, 312 insertions, 21 deletions
| diff --git a/CHANGES.md b/CHANGES.md index 5202eb3fe..8b7127a78 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ Ishtar changelog  - model: add history for document and containers  - remove "Administration" entry - put Account management in "Directory" entry  - Admin - Global variable: edit in table, add import/export in CSV/JSON +- Geo: create/edit form - new openlayers version - add default IGN tiles  ### Bug fixes ###  - find form: remove TAQ/TPQ check diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index 2018b8227..7ad89ed3e 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -30,7 +30,6 @@ from urllib.parse import urlparse, quote  import zipfile  from django import forms -from django.contrib.gis import forms as gis_forms  from django.conf import settings  from django.contrib.auth.models import User  from django.contrib.contenttypes.models import ContentType @@ -2780,7 +2779,7 @@ class GISForm(forms.ModelForm, CustomForm, ManageOldType):          for keys in self.GEO_FIELDS:              if any(key == self.geom_type for key in keys):                  map_srid = 4326 -                widget = gis_forms.OSMWidget +                widget = widgets.OSMWidget                  self.fields[keys[0]].widget = widget(                      attrs={"map_srid": map_srid,                             "cols": True, @@ -2812,7 +2811,7 @@ class GISForm(forms.ModelForm, CustomForm, ManageOldType):                  if keys[0] != "x":                      geom = getattr(instance, keys[0])                      map_srid = geom.srid or 4326 -                    widget = gis_forms.OSMWidget +                    widget = widgets.OSMWidget                      if map_srid == 4326:                          widget = widgets.ReversedOSMWidget                      self.fields[keys[0]].widget = widget( diff --git a/ishtar_common/static/gis/js/OLMapWidget.js b/ishtar_common/static/gis/js/OLMapWidget.js new file mode 100644 index 000000000..7102b0bf8 --- /dev/null +++ b/ishtar_common/static/gis/js/OLMapWidget.js @@ -0,0 +1,246 @@ +/* global ol */ + +var GeometryTypeControl = function(opt_options) { +    'use strict'; +    // Map control to switch type when geometry type is unknown +    var options = opt_options || {}; + +    var element = document.createElement('div'); +    element.className = 'switch-type type-' + options.type + ' ol-control ol-unselectable'; +    if (options.active) { +        element.className += " type-active"; +    } + +    var self = this; +    var switchType = function(e) { +        e.preventDefault(); +        if (options.widget.currentGeometryType !== self) { +            options.widget.map.removeInteraction(options.widget.interactions.draw); +            options.widget.interactions.draw = new ol.interaction.Draw({ +                features: options.widget.featureCollection, +                type: options.type +            }); +            options.widget.map.addInteraction(options.widget.interactions.draw); +            var className = options.widget.currentGeometryType.element.className.replace(/ type-active/g, ''); +            options.widget.currentGeometryType.element.className = className; +            options.widget.currentGeometryType = self; +            element.className += " type-active"; +        } +    }; + +    element.addEventListener('click', switchType, false); +    element.addEventListener('touchstart', switchType, false); + +    ol.control.Control.call(this, { +        element: element +    }); +}; + +// custom Ishtar - inherits not available anymore on new ol +// ol.inherits(GeometryTypeControl, ol.control.Control); +var ol_ext_inherits = function(child, parent) { +  child.prototype = Object.create(parent.prototype); +  child.prototype.constructor = child; +}; +ol_ext_inherits(GeometryTypeControl, ol.control.Control); +// end custom Ishtar + + +// TODO: allow deleting individual features (#8972) +(function() { +    'use strict'; +    var jsonFormat = new ol.format.GeoJSON(); + +    function MapWidget(options) { +        this.map = null; +        this.interactions = {draw: null, modify: null}; +        this.typeChoices = false; +        this.ready = false; + +        // Default options +        this.options = { +            default_lat: 0, +            default_lon: 0, +            default_zoom: 12, +            is_collection: options.geom_name.indexOf('Multi') > -1 || options.geom_name.indexOf('Collection') > -1 +        }; + +        // Altering using user-provided options +        for (var property in options) { +            if (options.hasOwnProperty(property)) { +                this.options[property] = options[property]; +            } +        } +        if (!options.base_layer) { +            this.options.base_layer = new ol.layer.Tile({source: new ol.source.OSM()}); +        } + +        this.map = this.createMap(); +        this.featureCollection = new ol.Collection(); +        this.featureOverlay = new ol.layer.Vector({ +            map: this.map, +            source: new ol.source.Vector({ +                features: this.featureCollection, +                useSpatialIndex: false // improve performance +            }), +            updateWhileAnimating: true, // optional, for instant visual feedback +            updateWhileInteracting: true // optional, for instant visual feedback +        }); + +        // Populate and set handlers for the feature container +        var self = this; +        this.featureCollection.on('add', function(event) { +            var feature = event.element; +            feature.on('change', function() { +                self.serializeFeatures(); +            }); +            if (self.ready) { +                self.serializeFeatures(); +                if (!self.options.is_collection) { +                    self.disableDrawing(); // Only allow one feature at a time +                } +            } +        }); + +        var initial_value = document.getElementById(this.options.id).value; +        if (initial_value) { +            var features = jsonFormat.readFeatures('{"type": "Feature", "geometry": ' + initial_value + '}'); +            var extent = ol.extent.createEmpty(); +            features.forEach(function(feature) { +                this.featureOverlay.getSource().addFeature(feature); +                ol.extent.extend(extent, feature.getGeometry().getExtent()); +            }, this); +            // Center/zoom the map +            this.map.getView().fit(extent, {maxZoom: this.options.default_zoom}); +        } else { +            this.map.getView().setCenter(this.defaultCenter()); +        } +        this.createInteractions(); +        if (initial_value && !this.options.is_collection) { +            this.disableDrawing(); +        } +        this.ready = true; +    } + +    MapWidget.prototype.createMap = function() { +    	// custom Ishtar +    	var layers = [this.options.base_layer]; +    	if (this.options.layers) layers = this.options.layers; +    	var options = { +            target: this.options.map_id, +            layers: layers, +            view: new ol.View({ +                zoom: this.options.default_zoom +            }) +    	}; +    	if (this.options.controls) options["controls"] = this.options.controls; +    	// end custom Ishtar +        var map = new ol.Map(options); +        return map; +    }; + +    MapWidget.prototype.createInteractions = function() { +        // Initialize the modify interaction +        this.interactions.modify = new ol.interaction.Modify({ +            features: this.featureCollection, +            deleteCondition: function(event) { +                return ol.events.condition.shiftKeyOnly(event) && +                    ol.events.condition.singleClick(event); +            } +        }); + +        // Initialize the draw interaction +        var geomType = this.options.geom_name; +        if (geomType === "Unknown" || geomType === "GeometryCollection") { +            // Default to Point, but create icons to switch type +            geomType = "Point"; +            this.currentGeometryType = new GeometryTypeControl({widget: this, type: "Point", active: true}); +            this.map.addControl(this.currentGeometryType); +            this.map.addControl(new GeometryTypeControl({widget: this, type: "LineString", active: false})); +            this.map.addControl(new GeometryTypeControl({widget: this, type: "Polygon", active: false})); +            this.typeChoices = true; +        } +        this.interactions.draw = new ol.interaction.Draw({ +            features: this.featureCollection, +            type: geomType +        }); + +        this.map.addInteraction(this.interactions.draw); +        this.map.addInteraction(this.interactions.modify); +    }; + +    MapWidget.prototype.defaultCenter = function() { +        var center = [this.options.default_lon, this.options.default_lat]; +        if (this.options.map_srid) { +            return ol.proj.transform(center, 'EPSG:4326', this.map.getView().getProjection()); +        } +        return center; +    }; + +    MapWidget.prototype.enableDrawing = function() { +        this.interactions.draw.setActive(true); +        if (this.typeChoices) { +            // Show geometry type icons +            var divs = document.getElementsByClassName("switch-type"); +            for (var i = 0; i !== divs.length; i++) { +                divs[i].style.visibility = "visible"; +            } +        } +    }; + +    MapWidget.prototype.disableDrawing = function() { +        if (this.interactions.draw) { +            this.interactions.draw.setActive(false); +            if (this.typeChoices) { +                // Hide geometry type icons +                var divs = document.getElementsByClassName("switch-type"); +                for (var i = 0; i !== divs.length; i++) { +                    divs[i].style.visibility = "hidden"; +                } +            } +        } +    }; + +    MapWidget.prototype.clearFeatures = function() { +        this.featureCollection.clear(); +        // Empty textarea widget +        document.getElementById(this.options.id).value = ''; +        this.enableDrawing(); +    }; + +    MapWidget.prototype.serializeFeatures = function() { +        // Three use cases: GeometryCollection, multigeometries, and single geometry +        var geometry = null; +        var features = this.featureOverlay.getSource().getFeatures(); +        if (this.options.is_collection) { +            if (this.options.geom_name === "GeometryCollection") { +                var geometries = []; +                for (var i = 0; i < features.length; i++) { +                    geometries.push(features[i].getGeometry()); +                } +                geometry = new ol.geom.GeometryCollection(geometries); +            } else { +                geometry = features[0].getGeometry().clone(); +                for (var j = 1; j < features.length; j++) { +                    switch (geometry.getType()) { +                    case "MultiPoint": +                        geometry.appendPoint(features[j].getGeometry().getPoint(0)); +                        break; +                    case "MultiLineString": +                        geometry.appendLineString(features[j].getGeometry().getLineString(0)); +                        break; +                    case "MultiPolygon": +                        geometry.appendPolygon(features[j].getGeometry().getPolygon(0)); +                    } +                } +            } +        } else { +            if (features[0]) { +                geometry = features[0].getGeometry(); +            } +        } +        document.getElementById(this.options.id).value = jsonFormat.writeGeometry(geometry); +    }; + +    window.MapWidget = MapWidget; +})(); diff --git a/ishtar_common/templates/gis/openlayers-osm.html b/ishtar_common/templates/gis/openlayers-osm.html index a6797a580..0c4391a0f 100644 --- a/ishtar_common/templates/gis/openlayers-osm.html +++ b/ishtar_common/templates/gis/openlayers-osm.html @@ -1,25 +1,50 @@  {% extends "gis/openlayers.html" %}  {% load l10n %} -{% block options %}{{ block.super }} -options['default_lon'] = {{ default_lon|unlocalize }}; -options['default_lat'] = {{ default_lat|unlocalize }}; -options['default_zoom'] = {{ default_zoom|unlocalize }}; -{% endblock %} -  {% block base_layer %} -var base_layer = new ol.layer.Tile({source: new ol.source.OSM()}); +        var base_layer;{% endblock %} + +{% block options %}{{ block.super }} +        options['default_lon'] = {{ default_lon|unlocalize }}; +        options['default_lat'] = {{ default_lat|unlocalize }}; +        options['default_zoom'] = {{ default_zoom|unlocalize }}; +        var map_layers_edit = [ +            new ol.layer.Group({ +                title: base_maps_msg, +                visible: true, +                layers: get_layers() +            }) +        ]; +        options["layers"] = map_layers_edit; +        delete options.base_layer; +        var map_controls = ol.control.defaults().extend([ +            new ol.control.FullScreen(), +            new ol.control.ScaleLine() +        ]); +        if (location.protocol == 'https:'){ +            map_controls.push( +                new TrackPositionControl({map_id: map_id}) +            ); +        } +        options["controls"] = map_controls;  {% endblock %}  {% block post_module %} -$(".ol-viewport").parent().width($(".form").width()); -$(document).ready(function() { -    {{ module }}.map.updateSize(); -    let features = {{ module }}.featureOverlay.getSource().getFeatures(); -    let layer_extent = features[0].getGeometry().getExtent().slice(0); -    features.forEach(function(feature){ ol.extent.extend(layer_extent, feature.getGeometry().getExtent())}); -    {{ module }}.map.getView().fit(layer_extent, {{ module }}.map.getSize()); -    let current_zoom = {{ module }}.map.getView().getZoom(); -    if ({{ module }}.map.getView().getZoom() > 18) {{ module }}.map.getView().setZoom(18); -}); +        var layer_switcher = new ol.control.LayerSwitcher({ +            tipLabel: 'Légende', +            groupSelectStyle: 'children' +        }); +        {{ module }}.map.addControl(layer_switcher); +        $(".ol-viewport").parent().width($(".form").width()); +        $(document).ready(function() { +            {{ module }}.map.updateSize(); +            let features = {{ module }}.featureOverlay.getSource().getFeatures(); +            if (features.length){ +                let layer_extent = features[0].getGeometry().getExtent().slice(0); +                features.forEach(function(feature){ ol.extent.extend(layer_extent, feature.getGeometry().getExtent())}); +                {{ module }}.map.getView().fit(layer_extent, {{ module }}.map.getSize()); +            } +            let current_zoom = {{ module }}.map.getView().getZoom(); +            if ({{ module }}.map.getView().getZoom() > 18) {{ module }}.map.getView().setZoom(18); +        });  {% endblock %} diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index 57dc1a730..2bcc598bf 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -24,6 +24,7 @@ import logging  from django import forms  from django.conf import settings  from django.contrib.gis import forms as gis_forms +from django.contrib.gis.db import models as gis_models  from django.core.exceptions import ValidationError  from django.core.files import File  from django.db.models import fields @@ -1333,7 +1334,26 @@ class RangeInput(NumberInput):      input_type = "range" -class ReversedOSMWidget(gis_forms.OSMWidget): +class OSMWidget(gis_forms.OSMWidget): +    """ +    Replace Media with local ressource +    """ +    class Media: +        extend = False +        css = { +            'all': ( +                "ol/ol.css", +                'gis/css/ol3.css', +            ) +        } +        js = ( +            "ol/ol.js", +            "ol-layerswitcher/ol-layerswitcher.js", +            "gis/js/OLMapWidget.js", +        ) + + +class ReversedOSMWidget(OSMWidget):      def get_context(self, name, value, attrs):          if value:              if not isinstance(value, str):  # should be geo | 
