224 lines
7.0 KiB
JavaScript
224 lines
7.0 KiB
JavaScript
/* Copyright (c) 2019 Jean-Marc VIGLINO,
|
|
released under the CeCILL-B license (French BSD license)
|
|
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
|
|
*/
|
|
import ol_Feature from 'ol/Feature.js'
|
|
import ol_geom_Polygon from 'ol/geom/Polygon.js'
|
|
import ol_source_Vector from 'ol/source/Vector.js'
|
|
import { ol_coordinate_getFeatureCenter } from "../geom/GeomUtils.js";
|
|
import './Vector.js'
|
|
|
|
/** Abstract base class; normally only used for creating subclasses. Bin collector for data
|
|
* @constructor
|
|
* @extends {ol.source.Vector}
|
|
* @param {Object} options ol_source_VectorOptions + grid option
|
|
* @param {ol.source.Vector} options.source Source
|
|
* @param {boolean} options.listenChange listen changes (move) on source features to recalculate the bin, default true
|
|
* @param {fucntion} [options.geometryFunction] Function that takes an ol.Feature as argument and returns an ol.geom.Point as feature's center.
|
|
* @param {function} [options.flatAttributes] Function takes a bin and the features it contains and aggragate the features in the bin attributes when saving
|
|
*/
|
|
var ol_source_BinBase = class olsourceBinBase extends ol_source_Vector {
|
|
constructor(options) {
|
|
options = options || {};
|
|
super(options);
|
|
|
|
this._bindModify = this._onModifyFeature.bind(this);
|
|
this._watch = true;
|
|
this._origin = options.source;
|
|
this._listen = (options.listenChange !== false);
|
|
|
|
// Geometry function
|
|
this._geomFn = options.geometryFunction || ol_coordinate_getFeatureCenter || function (f) { return f.getGeometry().getFirstCoordinate(); };
|
|
|
|
// Future features
|
|
this._origin.on('addfeature', this._onAddFeature.bind(this));
|
|
this._origin.on('removefeature', this._onRemoveFeature.bind(this));
|
|
this._origin.on('clearstart', this._onClearFeature.bind(this));
|
|
this._origin.on('clearend', this._onClearFeature.bind(this));
|
|
if (typeof (options.flatAttributes) === 'function') {
|
|
this._flatAttributes = options.flatAttributes;
|
|
}
|
|
|
|
// Handle exsting feature (should be called from children when fully created)
|
|
// this.reset()
|
|
}
|
|
/**
|
|
* On add feature
|
|
* @param {ol.events.Event} e
|
|
* @param {ol.Feature} bin
|
|
* @private
|
|
*/
|
|
_onAddFeature(e, bin, listen) {
|
|
var f = e.feature || e.target;
|
|
bin = bin || this.getBinAt(this._geomFn(f), true);
|
|
if (bin)
|
|
bin.get('features').push(f);
|
|
if (this._listen && listen !== false)
|
|
f.on('change', this._bindModify);
|
|
}
|
|
/**
|
|
* On remove feature
|
|
* @param {ol.events.Event} e
|
|
* @param {ol.Feature} bin
|
|
* @private
|
|
*/
|
|
_onRemoveFeature(e, bin, listen) {
|
|
if (!this._watch)
|
|
return;
|
|
var f = e.feature || e.target;
|
|
bin = bin || this.getBinAt(this._geomFn(f));
|
|
if (bin) {
|
|
// Remove feature from bin
|
|
var features = bin.get('features');
|
|
for (var i = 0, fi; fi = features[i]; i++) {
|
|
if (fi === f) {
|
|
features.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
// Remove bin if no features
|
|
if (!features.length) {
|
|
this.removeFeature(bin);
|
|
}
|
|
} else {
|
|
// console.log("[ERROR:Bin] remove feature: feature doesn't exists anymore.");
|
|
}
|
|
if (this._listen && listen !== false)
|
|
f.un('change', this._bindModify);
|
|
}
|
|
/** When clearing features remove the listener
|
|
* @private
|
|
*/
|
|
_onClearFeature(e) {
|
|
if (e.type === 'clearstart') {
|
|
if (this._listen) {
|
|
this._origin.getFeatures().forEach(function (f) {
|
|
f.un('change', this._bindModify);
|
|
}.bind(this));
|
|
}
|
|
this.clear();
|
|
this._watch = false;
|
|
} else {
|
|
this._watch = true;
|
|
}
|
|
}
|
|
/**
|
|
* Get the bin that contains a feature
|
|
* @param {ol.Feature} f the feature
|
|
* @return {ol.Feature} the bin or null it doesn't exit
|
|
*/
|
|
getBin(feature) {
|
|
var bins = this.getFeatures();
|
|
for (var i = 0, b; b = bins[i]; i++) {
|
|
var features = b.get('features');
|
|
for (var j = 0, f; f = features[j]; j++) {
|
|
if (f === feature)
|
|
return b;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/** Get the grid geometry at the coord
|
|
* @param {ol.Coordinate} coord
|
|
* @param {Object} attributes add key/value to this object to add properties to the grid feature
|
|
* @returns {ol.geom.Polygon}
|
|
* @api
|
|
*/
|
|
getGridGeomAt(coord /*, attributes */) {
|
|
return new ol_geom_Polygon([coord]);
|
|
}
|
|
/** Get the bean at a coord
|
|
* @param {ol.Coordinate} coord
|
|
* @param {boolean} create true to create if doesn't exit
|
|
* @return {ol.Feature} the bin or null it doesn't exit
|
|
*/
|
|
getBinAt(coord, create) {
|
|
var attributes = {};
|
|
var g = this.getGridGeomAt(coord, attributes);
|
|
if (!g)
|
|
return null;
|
|
var center = g.getInteriorPoint ? g.getInteriorPoint().getCoordinates() : g.getInteriorPoints().getCoordinates()[0]; // ol_extent_getCenter(g.getExtent());
|
|
var features = this.getFeaturesAtCoordinate(center);
|
|
var bin = features[0];
|
|
if (!bin && create) {
|
|
attributes.geometry = g;
|
|
attributes.features = [];
|
|
attributes.center = center;
|
|
bin = new ol_Feature(attributes);
|
|
this.addFeature(bin);
|
|
}
|
|
return bin || null;
|
|
}
|
|
/**
|
|
* A feature has been modified
|
|
* @param {ol.events.Event} e
|
|
* @private
|
|
*/
|
|
_onModifyFeature(e) {
|
|
var bin = this.getBin(e.target);
|
|
var bin2 = this.getBinAt(this._geomFn(e.target), 'create');
|
|
if (bin !== bin2) {
|
|
// remove from the bin
|
|
if (bin) {
|
|
this._onRemoveFeature(e, bin, false);
|
|
}
|
|
// insert in the new bin
|
|
if (bin2) {
|
|
this._onAddFeature(e, bin2, false);
|
|
}
|
|
}
|
|
this.changed();
|
|
}
|
|
/** Clear all bins and generate a new one.
|
|
*/
|
|
reset() {
|
|
this.clear();
|
|
var features = this._origin.getFeatures();
|
|
for (var i = 0, f; f = features[i]; i++) {
|
|
this._onAddFeature({ feature: f });
|
|
}
|
|
this.changed();
|
|
}
|
|
/**
|
|
* Get features without circular dependencies (vs. getFeatures)
|
|
* @return {Array<ol.Feature>}
|
|
*/
|
|
getGridFeatures() {
|
|
var features = [];
|
|
this.getFeatures().forEach(function (f) {
|
|
var bin = new ol_Feature(f.getGeometry().clone());
|
|
for (var i in f.getProperties()) {
|
|
if (i !== 'features' && i !== 'geometry') {
|
|
bin.set(i, f.get(i));
|
|
}
|
|
}
|
|
bin.set('nb', f.get('features').length);
|
|
this._flatAttributes(bin, f.get('features'));
|
|
features.push(bin);
|
|
}.bind(this));
|
|
return features;
|
|
}
|
|
/** Create bin attributes using the features it contains when exporting
|
|
* @param {ol.Feature} bin the bin to export
|
|
* @param {Array<ol.Features>} features the features it contains
|
|
*/
|
|
_flatAttributes( /*bin, features*/) {
|
|
}
|
|
/** Set the flatAttribute function
|
|
* @param {function} fn Function that takes a bin and the features it contains and aggragate the features in the bin attributes when saving
|
|
*/
|
|
setFlatAttributesFn(fn) {
|
|
if (typeof (fn) === 'function')
|
|
this._flatAttributes = fn;
|
|
}
|
|
/**
|
|
* Get the orginal source
|
|
* @return {ol_source_Vector}
|
|
*/
|
|
getSource() {
|
|
return this._origin;
|
|
}
|
|
}
|
|
|
|
export default ol_source_BinBase
|