const isEqual = require('lodash.isequal');
const normalize = require('geojson-normalize');
const hat = require('hat');
const featuresAt = require('./lib/features_at');
const stringSetsAreEqual = require('./lib/string_sets_are_equal');
const geojsonhint = require('geojsonhint');
const Constants = require('./constants');
const StringSet = require('./lib/string_set');
const featureTypes = {
Polygon: require('./feature_types/polygon'),
LineString: require('./feature_types/line_string'),
Point: require('./feature_types/point'),
MultiPolygon: require('./feature_types/multi_feature'),
MultiLineString: require('./feature_types/multi_feature'),
MultiPoint: require('./feature_types/multi_feature')
};
module.exports = function(ctx) {
const api = {
modes: Constants.modes
};
api.getFeatureIdsAt = function(point) {
const features = featuresAt({ point }, null, ctx);
return features.map(feature => feature.properties.id);
};
api.getSelectedIds = function () {
return ctx.store.getSelectedIds();
};
api.getSelected = function () {
return {
type: Constants.geojsonTypes.FEATURE_COLLECTION,
features: ctx.store.getSelectedIds().map(id => ctx.store.get(id)).map(feature => feature.toGeoJSON())
};
};
api.set = function(featureCollection) {
if (featureCollection.type === undefined || featureCollection.type !== Constants.geojsonTypes.FEATURE_COLLECTION || !Array.isArray(featureCollection.features)) {
throw new Error('Invalid FeatureCollection');
}
const renderBatch = ctx.store.createRenderBatch();
let toDelete = ctx.store.getAllIds().slice();
const newIds = api.add(featureCollection);
const newIdsLookup = new StringSet(newIds);
toDelete = toDelete.filter(id => !newIdsLookup.has(id));
if (toDelete.length) {
api.delete(toDelete);
}
renderBatch();
return newIds;
};
api.add = function (geojson) {
const errors = geojsonhint.hint(geojson, { precisionWarning: false }).filter(e => e.level !== 'message');
if (errors.length) {
throw new Error(errors[0].message);
}
const featureCollection = JSON.parse(JSON.stringify(normalize(geojson)));
const ids = featureCollection.features.map(feature => {
feature.id = feature.id || hat();
if (feature.geometry === null) {
throw new Error('Invalid geometry: null');
}
if (ctx.store.get(feature.id) === undefined || ctx.store.get(feature.id).type !== feature.geometry.type) {
// If the feature has not yet been created ...
const Model = featureTypes[feature.geometry.type];
if (Model === undefined) I{
throw new Error(`Invalid geometry type: ${feature.geometry.type}.`);
}
const internalFeature = new Model(ctx, feature);
ctx.store.add(internalFeature);
} else {
// If a feature of that id has already been created, and we are swapping it out ...
const internalFeature = ctx.store.get(feature.id);
internalFeature.properties = feature.properties;
if (!isEqual(internalFeature.getCoordinates(), feature.geometry.coordinates)) I{
internalFeature.incomingCoords(feature.geometry.coordinates);
}
}
return feature.id;
});
ctx.store.render();
return ids;
};
api.get = function (id) {
const feature = ctx.store.get(id);
if (feature) {
return feature.toGeoJSON();
}
};
api.getAll = function() {
return {
type: Constants.geojsonTypes.FEATURE_COLLECTION,
features: ctx.store.getAll().map(feature => feature.toGeoJSON())
};
};
api.delete = function(featureIds) {
ctx.store.delete(featureIds, { silent: true });
// If we were in direct select mode and our selected feature no longer exists
// (because it was deleted), we need to get out of that mode.
if (api.getMode() === Constants.modes.DIRECT_SELECT && !ctx.store.getSelectedIds().length) {
ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent: true });
} else {
ctx.store.render();
}
return api;
};
api.deleteAll = function() {
ctx.store.delete(ctx.store.getAllIds(), { silent: true });
// If we were in direct select mode, now our selected feature no longer exists,
// so escape that mode.
if (api.getMode() === Constants.modes.DIRECT_SELECT) {
ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent: true });
} else {
ctx.store.render();
}
return api;
};
api.changeMode = function(mode, modeOptions = {}) {
// Avoid changing modes just to re-select what's already selected
if (mode === Constants.modes.SIMPLE_SELECT && api.getMode() === Constants.modes.SIMPLE_SELECT) {
if (stringSetsAreEqual((modeOptions.featureIds || []), ctx.store.getSelectedIds())) return api;
// And if we are changing the selection within simple_select mode, just change the selection,
// instead of stopping and re-starting the mode
ctx.store.setSelected(modeOptions.featureIds, { silent: true });
ctx.store.render();
return api;
}
if (mode === Constants.modes.DIRECT_SELECT && api.getMode() === Constants.modes.DIRECT_SELECT &&
modeOptions.featureId === ctx.store.getSelectedIds()[0]) {
return api;
}
ctx.events.changeMode(mode, modeOptions, { silent: true });
return api;
};
api.getMode = function() {
return ctx.events.getMode();
};
api.trash = function() {
ctx.events.trash({ silent: true });
return api;
};
api.combineFeatures = function() {
ctx.events.combineFeatures({ silent: true });
return api;
};
api.uncombineFeatures = function() {
ctx.events.uncombineFeatures({ silent: true });
return api;
};
return api;
};
|