diff options
Diffstat (limited to 'base_geoengine/static/src/js/widgets/geoengine_widgets.js')
| -rw-r--r-- | base_geoengine/static/src/js/widgets/geoengine_widgets.js | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/base_geoengine/static/src/js/widgets/geoengine_widgets.js b/base_geoengine/static/src/js/widgets/geoengine_widgets.js new file mode 100644 index 0000000..bca843c --- /dev/null +++ b/base_geoengine/static/src/js/widgets/geoengine_widgets.js @@ -0,0 +1,586 @@ +/* --------------------------------------------------------- + * Odoo base_geoengine + * Author B.Binet Copyright Camptocamp SA + * Contributor N. Bessi Copyright Camptocamp SA + * Contributor Laurent Mignon 2015 Acsone SA/NV + * Contributor Yannick Vaucher 2015-2016 Camptocamp SA + * License in __manifest__.py at root level of the module + * --------------------------------------------------------- + */ +odoo.define('base_geoengine.geoengine_widgets', function (require) { + "use strict"; + + var core = require('web.core'); + var AbstractField = require('web.AbstractField'); + var geoengine_common = require('base_geoengine.geoengine_common'); + var BackgroundLayers = require('base_geoengine.BackgroundLayers'); + var field_registry = require('web.field_registry'); + + var FieldGeoEngineEditMap = AbstractField.extend(geoengine_common.GeoengineMixin, { // eslint-disable-line max-len + template: 'FieldGeoEngineEditMap', + + geoType: null, + map: null, + defaultExtent: null, + format: null, + vectorLayer: null, + rasterLayers: null, + source: null, + features: null, + drawControl: null, + modifyControl: null, + tabListenerInstalled: false, + bgLayers: new BackgroundLayers(), + + // -------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------- + + /** + * @override + */ + start: function () { + var def = this._super(); + + // Add a listener on parent tab if it exists in order to refresh + // geoengine view we need to trigger it on DOM update for changes + // from view to edit mode. + core.bus.on('DOM_updated', this, function () { + this._addTabListener(); + }.bind(this)); + + return def; + }, + + // FIXME still used? + validate: function () { + this.invalid = false; + }, + + // -------------------------------------------------------------------- + // Private + // -------------------------------------------------------------------- + + _createVectorLayer: function () { + this.features = new ol.Collection(); + this.source = new ol.source.Vector({features: this.features}); + return new ol.layer.Vector({ + source: this.source, + style: new ol.style.Style({ + fill: new ol.style.Fill({ + color: '#ee9900', + opacity: 0.7, + }), + stroke: new ol.style.Stroke({ + color: '#ee9900', + width: 3, + opacity: 1, + }), + image: new ol.style.Circle({ + radius: 7, + fill: new ol.style.Fill({ + color: '#ffcc33', + }), + }), + }), + }); + }, + + _createLayers: function (field_infos) { + this.vectorLayer = this._createVectorLayer(); + this.rasterLayers = this.bgLayers.create([ + field_infos.edit_raster, + ]); + if (this.rasterLayers.length) { + this.rasterLayers[0].isBaseLayer = true; + } + }, + + _addTabListener: function () { + if (this.tabListenerInstalled) { + return; + } + var tab = this.$el.closest('div.tab-pane'); + if (!tab.length) { + return; + } + var tab_link = $('a[href="#' + tab[0].id + '"]') + if (!tab_link.length) { + return; + } + tab_link.on('shown.bs.tab', function (e) { + this._render(); + }.bind(this)); + this.tabListenerInstalled = true; + }, + + _parseValue: function (value) { + return value; + }, + + _updateMapEmpty: function () { + var map_view = this.map.getView(); + // Default extent + if (map_view) { + var extent = this.defaultExtent.replace(/\s/g, '').split(','); + extent = extent.map(coord => Number(coord)); + map_view.fit(extent, {maxZoom: this.defaultZoom || 5}); + } + }, + + _updateMapZoom: function (zoom) { + + var map_zoom = typeof zoom === 'undefined' ? true : zoom; + + if (this.source) { + var extent = this.source.getExtent(); + var infinite_extent = [ + Infinity, Infinity, -Infinity, -Infinity, + ]; + if (map_zoom && extent !== infinite_extent) { + var map_view = this.map.getView(); + if (map_view) { + map_view.fit(extent, {maxZoom: 15}); + } + } + } + }, + + _setValue: function (value, zoom) { + + this._super(value); + this.value = value; + + if (this.map) { + + var ft = new ol.Feature({ + geometry: new ol.format.GeoJSON().readGeometry(value), + labelPoint: new ol.format.GeoJSON().readGeometry(value), + }); + this.source.clear(); + this.source.addFeature(ft); + if (value) { + this._updateMapZoom(zoom); + } else { + this._updateMapEmpty(); + } + } + }, + + _isTabVisible: function () { + var tab = this.$el.closest('div.tab-pane'); + if (!tab.length) { + return false; + } + return tab.is(":visible"); + }, + + _onUIChange: function () { + var value = null; + if (this._geometry) { + value = this.format.writeGeometry(this._geometry); + } + this._setValue(value, false); + }, + + _setupControls: function () { + + /* Add a draw interaction depending on geoType of the field + * plus adds a modify interaction to be able to change line + * and polygons. + * As modify needs to get pointer position on map it requires + * the map to be rendered before being created + */ + var handler = null; + if (this.geoType === 'POLYGON') { + handler = "Polygon"; + } else if (this.geoType === 'MULTIPOLYGON') { + handler = "MultiPolygon"; + } else if (this.geoType === 'LINESTRING') { + handler = "LineString"; + } else if (this.geoType === 'MULTILINESTRING') { + handler = "MultiLineString"; + } else if (this.geoType === 'POINT') { + handler = "Point"; + } else if (this.geoType === 'MULTIPOINT') { + handler = "MultiPoint"; + } else { + // FIXME: unsupported geo type + } + + var drawControl = function (options) { + ol.interaction.Draw.call(this, options); + }; + ol.inherits(drawControl, ol.interaction.Draw); + drawControl.prototype.finishDrawing = function () { + this.source_.clear(); + ol.interaction.Draw.prototype.finishDrawing.call(this); + }; + + this.drawControl = new drawControl({ + source: this.source, + type: handler, + }); + this.map.addInteraction(this.drawControl); + var onchange_geom = function (e) { + // Trigger onchanges when drawing is done + if (e.type === 'drawend') { + this._geometry = e.feature.getGeometry(); + } else { + // Modify end + this._geometry = e.features.item(0).getGeometry(); + } + this._onUIChange(); + }.bind(this); + this.drawControl.on('drawend', onchange_geom); + + this.features = this.source.getFeaturesCollection(); + this.modifyControl = new ol.interaction.Modify({ + features: this.features, + // The SHIFT key must be pressed to delete vertices, so + // that new vertices can be drawn at the same position + // of existing vertices + deleteCondition: function (event) { + return ol.events.condition.shiftKeyOnly(event) && + ol.events.condition.singleClick(event); + }, + }); + this.map.addInteraction(this.modifyControl); + this.modifyControl.on('modifyend', onchange_geom); + + var self = this; + var ClearMapControl = function (opt_options) { + var options = opt_options || {}; + var button = document.createElement('button'); + button.innerHTML = '<i class="fa fa-trash"/>'; + button.addEventListener('click', function () { + self.source.clear(); + self._geometry = null; + self._onUIChange(); + }); + var element = document.createElement('div'); + element.className = 'ol-clear ol-unselectable ol-control'; + element.appendChild(button); + + ol.control.Control.call(this, { + element: element, + target: options.target, + }); + }; + ol.inherits(ClearMapControl, ol.control.Control); + this.clearmapControl = new ClearMapControl(); + this.map.addControl(this.clearmapControl); + }, + + _renderMap: function () { + if (!this.map) { + var $el = this.$el[0]; + $($el).css({width: '100%', height: '100%'}); + this.map = new ol.Map({ + layers: this.rasterLayers, + target: $el, + view: new ol.View({ + center: [0, 0], + zoom: 5, + }), + }); + this.map.addLayer(this.vectorLayer); + + this.format = new ol.format.GeoJSON({ + internalProjection: this.map.getView().getProjection(), + externalProjection: 'EPSG:' + this.srid, + }); + + $(document).trigger('FieldGeoEngineEditMap:ready', [this.map]); + this._setValue(this.value); + + if (this.mode !== 'readonly' && + !this.get('effective_readonly')) { + this._setupControls(); + this.drawControl.setActive(true); + this.modifyControl.setActive(true); + this.clearmapControl.element.children[0].disabled = false; + } + } + }, + + _render: function () { + this._rpc({ + model: this.model, + method: 'get_edit_info_for_geo_column', + args: [this.name], + }).then(function (result) { + this._createLayers(result); + this.geoType = result.geo_type; + this.projection = result.projection; + this.defaultExtent = result.default_extent; + this.defaultZoom = result.default_zoom; + this.restrictedExtent = result.restricted_extent; + this.srid = result.srid; + if (this.$el.is(":visible") || this._isTabVisible()) { + this._renderMap(); + } + }.bind(this)); + }, + }); + + // TODO migrate the following widgets + + var FieldGeoPointXY = AbstractField.extend({ + template: 'FieldGeoPointXY', + + start: function () { + this._super.apply(this, arguments); + this.$input = this.$el.find('input'); + this.$input.change(this._onUIChange); + this.setupFocus(this.$input); + }, + + get_coords: function () { + + /* Get coordinates and check it has the right format + * + * @return [x, y] + */ + var x = openerp.web.parse_value(this.$input.eq(0).val(), { + type: 'float', + }); + var y = openerp.web.parse_value(this.$input.eq(1).val(), { + type: 'float', + }); + return [x, y]; + }, + + make_GeoJSON: function (coords) { + return {"type": "Point", "coordinates": coords}; + }, + + _setValue: function (value) { + this._super.apply(this, arguments); + + if (value) { + var geo_obj = JSON.parse(value); + this.$input.eq(0).val(geo_obj.coordinates[0]); + this.$input.eq(1).val(geo_obj.coordinates[1]); + } else { + this.$input.val(''); + } + }, + + _onUIChange: function () { + var coords = this.get_coords(); + if (coords[0] && coords[1]) { + var json = this.make_GeoJSON(coords); + this.value = JSON.stringify(json); + } else { + this.value = false; + } + }, + + validate: function () { + this.invalid = false; + try { + // Get coords to check if floats + var coords = this.get_coords(); + + // Make sure the two coordinates are set or None + this.invalid = this.required && + (coords[0] === 0 || coords[1] === 0 ) || + coords[0] === false && coords[1] !== false || + coords[0] !== false && coords[1] === false; + } catch (e) { + this.invalid = true; + } + }, + + update_dom: function () { + this._super.apply(this, arguments); + this.set_readonly(this.readonly); + }, + + set_readonly: function () { + this.$input.prop('readonly', this.readonly); + }, + }); + + var FieldGeoPointXYReadonly = FieldGeoPointXY.extend({ + template: 'FieldGeoPointXY.readonly', + + _setValue: function (value) { + this._super.apply(this, arguments); + var show_value = ''; + if (value) { + var geo_obj = JSON.parse(value); + show_value = "(" + geo_obj.coordinates[0] + ", " + + geo_obj.coordinates[1] + ")"; + } + this.$el.find('div').text(show_value); + return show_value; + }, + + validate: function () { + this.invalid = false; + }, + }); + + var FieldGeoRect = AbstractField.extend({ + template: 'FieldGeoRect', + + start: function () { + this._super.apply(this, arguments); + this.$input = this.$el.find('input'); + this.$input.change(this._onUIChange); + this.setupFocus(this.$input); + }, + + get_coords: function () { + + /* Get coordinates in the input fields + * + * @return [[x1, y1],[x2, y2]] + */ + var x1 = openerp.web.parse_value(this.$input.eq(0).val(), { + type: 'float', + }); + var y1 = openerp.web.parse_value(this.$input.eq(1).val(), { + type: 'float', + }); + var x2 = openerp.web.parse_value(this.$input.eq(2).val(), { + type: 'float', + }); + var y2 = openerp.web.parse_value(this.$input.eq(3).val(), { + type: 'float', + }); + + return [[x1, y1], [x2, y2]]; + }, + + make_GeoJSON: function (coords) { + var p1 = coords[0]; + var p2 = [coords[0][0], coords[1][1]]; + var p3 = coords[1]; + var p4 = [coords[1][0], coords[0][1]]; + // Create a loop in clockwise + var points = [[ p1, p2, p3, p4, p1 ]]; + return {"type": "Polygon", "coordinates": points}; + }, + + _setValue: function (value) { + this._super.apply(this, arguments); + + if (value) { + var geo_obj = JSON.parse(value); + this.$input.eq(0).val(geo_obj.coordinates[0][0][0]); + this.$input.eq(1).val(geo_obj.coordinates[0][0][1]); + this.$input.eq(2).val(geo_obj.coordinates[0][2][0]); + this.$input.eq(3).val(geo_obj.coordinates[0][2][1]); + } else { + this.$input.val(''); + } + }, + + correct_bounds: function (coords) { + + /* Reverse bounds if the upper right + * point is smaller than bottom left + * + * @return [[x1, y1],[x2, y2]] + */ + var x1 = coords[0][0], + y1 = coords[0][1], + x2 = coords[1][0], + y2 = coords[1][1]; + + var minx = Math.min(x1, x2); + var maxx = Math.max(x1, x2); + + var miny = Math.min(y1, y2); + var maxy = Math.max(y1, y2); + + return [[minx, miny], [maxx, maxy]]; + }, + + _onUIChange: function () { + var coords = this.get_coords(); + if (this.all_are_set(coords)) { + coords = this.correct_bounds(coords); + var json = this.make_GeoJSON(coords); + this.value = JSON.stringify(json); + } else { + this.value = false; + } + }, + + all_are_set: function (coords) { + return coords[0][0] !== false && coords[0][1] !== false && + coords[1][0] !== false && coords[1][1] !== false; + }, + + none_are_set: function (coords) { + return coords[0][0] === false && coords[0][1] === false && + coords[1][0] === false && coords[1][1] === false; + }, + + validate: function () { + this.invalid = false; + try { + // Get coords to check if floats + var coords = this.get_coords(); + + // Make sure all the coordinates are set + // if not None or if required + this.invalid = (this.required || + !this.none_are_set(coords)) && + !this.all_are_set(coords); + } catch (e) { + this.invalid = true; + } + }, + + update_dom: function () { + this._super.apply(this, arguments); + this.set_readonly(this.readonly); + }, + + set_readonly: function () { + this.$input.prop('readonly', this.readonly); + }, + }); + + var FieldGeoRectReadonly = FieldGeoRect.extend({ + template: 'FieldGeoRect.readonly', + + _setValue: function (value) { + this._super.apply(this, arguments); + var show_value = ''; + if (value) { + var geo_obj = JSON.parse(value); + show_value = "(" + geo_obj.coordinates[0][0][0] + ", " + + geo_obj.coordinates[0][0][1] + "), " + + "(" + geo_obj.coordinates[0][2][0] + ", " + + geo_obj.coordinates[0][2][1] + ")"; + } + this.$el.find('div').text(show_value); + return show_value; + }, + + validate: function () { + this.invalid = false; + }, + }); + + field_registry.add('geo_edit_map', FieldGeoEngineEditMap); + // .add('geo_point_xy', FieldGeoPointXY) + // .add('geo_point_xy', FieldGeoPointXYReadonly) + // .add('geo_rect', FieldGeoRect) + // .add('geo_rect', FieldGeoRectReadonly); + + return { + FieldGeoEngineEditMap: FieldGeoEngineEditMap, + // FieldGeoPointXY: FieldGeoPointXY, + // FieldGeoPointXYReadonly: FieldGeoPointXYReadonly, + // FieldGeoRect: FieldGeoRect, + // FieldGeoRectReadonly: FieldGeoRectReadonly, + }; + +}); |
