src/api/API.js
import $ from 'jquery'
import WKT from 'ol/format/WKT'
import { get as getProj, transform, transformExtent } from 'ol/proj'
import BaseObject from 'ol/Object'
import { boundingExtent } from 'ol/extent'
import { cssClasses, keyCodes } from '../globals'
import { Debug } from '../Debug'
import { FeatureAPI } from './FeatureAPI'
import { GeometryAPI } from './GeometryAPI'
import { KMLAPI } from './KMLAPI'
import { MeasureAPI } from './MeasureAPI'
import { PlacesAPI } from './PlacesAPI'
import { PrintToScaleAPI } from './PrintToScaleAPI'
// NOTE:
// Access to a source factory would be nice
/**
* @typedef {object} APIOptions
* @property {StyleLike} [drawStyle='#drawStyle']
*/
export class API extends BaseObject {
/**
* @param {G4UMap} map
* @param {object} options
*/
constructor (map, options = {}) {
super()
/**
* @type {boolean}
* @private
*/
this.featureManipulationActive_ = false
this.extentPadding_ = options.extentPadding
/**
* @type {StyleLike}
* @private
*/
this.drawStyle_ = options.drawStyle || '#drawStyle'
/**
* @type {G4UMap}
* @private
*/
this.map_ = map
this.map_.once('ready', () => {
this.layerConfigurator_ = this.map_.get('configurator').layerConfigurator_
})
$(this.map_.getViewport()).on('keydown', this.onKeyDown_.bind(this))
this.geometryAPI_ = new GeometryAPI(this, map)
this.printToScaleAPI_ = new PrintToScaleAPI(this, map)
this.featureAPI_ = new FeatureAPI(this, map)
this.kmlAPI_ = new KMLAPI(this, map)
this.measureAPI_ = new MeasureAPI(this, map)
this.placesAPI_ = new PlacesAPI(this, map)
this.setApiAccessObject()
}
cancelInteractions () {
this.dispatchEvent('cancelInteractions')
}
getDrawStyle () {
return this.drawStyle_
}
// printToScale: {
// showFrame: this.showPrintToScaleFrame.bind(this),
// getFrame: this.getPrintToScaleFrame.bind(this),
// hideFrame: this.hidePrintToScaleFrame.bind(this)
// },
setApiAccessObject () {
const api = {
cancelInteractions: this.cancelInteractions.bind(this),
geometry: {
draw: this.geometryAPI_.drawGeometry.bind(this.geometryAPI_),
show: this.geometryAPI_.showGeometry.bind(this.geometryAPI_),
hide: this.geometryAPI_.hideGeometry.bind(this.geometryAPI_),
clear: this.geometryAPI_.clear.bind(this.geometryAPI_),
extent: this.geometryAPI_.getExtent.bind(this.geometryAPI_)
},
printToScale: {
showFrame: this.printToScaleAPI_.showFrame.bind(this.printToScaleAPI_),
getFrame: this.printToScaleAPI_.getFrame.bind(this.printToScaleAPI_),
hideFrame: this.printToScaleAPI_.hideFrame.bind(this.printToScaleAPI_)
},
feature: {
// create: this.createFeature.bind(this),
// show: this.showFeature.bind(this),
// hide: this.hideFeature.bind(this)
modify: this.featureAPI_.modifyFeature.bind(this.featureAPI_),
select: this.featureAPI_.selectFeature.bind(this.featureAPI_),
draw: this.featureAPI_.drawFeature.bind(this.featureAPI_)
},
kml: {
add: this.kmlAPI_.add.bind(this.kmlAPI_),
update: this.kmlAPI_.update.bind(this.kmlAPI_),
remove: this.kmlAPI_.remove.bind(this.kmlAPI_)
},
places: {
add: this.placesAPI_.addPlace.bind(this.placesAPI_),
clear: this.placesAPI_.clear.bind(this.placesAPI_),
openPopup: this.placesAPI_.openPopup.bind(this.placesAPI_),
closePopup: this.placesAPI_.closePopup.bind(this.placesAPI_),
hasPopup: this.placesAPI_.hasPopup.bind(this.placesAPI_),
onPopup: this.placesAPI_.onPopup.bind(this.placesAPI_),
extent: this.placesAPI_.getExtent.bind(this.placesAPI_),
activate: this.placesAPI_.activate.bind(this.placesAPI_),
deactivate: this.placesAPI_.deactivate.bind(this.placesAPI_),
isActive: this.placesAPI_.isActive.bind(this.placesAPI_),
style: this.placesAPI_.changeStyle.bind(this.placesAPI_),
cluster: this.placesAPI_.activateClustering.bind(this.placesAPI_)
},
// 'userInteraction': {
// 'get': {
// 'position': function (callback, options) {
// options = options || {}
//
// const savedCursorStyle = $(this.map_.getViewport()).css('cursor')
// $(this.map_.getViewport()).css('cursor', options.pointerStyle || 'pointer')
//
// this.map_.once('singleclick', (e) => {
// $(this.map_.getViewport()).css('cursor', savedCursorStyle)
// var coordinate = e.coordinate
// if (options.projection) {
// coordinate = transform(
// coordinate,
// options.map.getView().getProjection(),
// options.projection
// )
// }
// callback(coordinate)
// })
// }
// }
// },
// 'layer': {
// 'add': map.addLayer,
// 'addFeature': function(layer, feature) {
// layer.getSource().addFeature(feature)
// },
// 'base': {
// 'add': api.addBaseLayer,
// 'setVisible': api.setVisibleBaseLayer
// },
// 'feature': {
// 'add': api.addFeatureLayer
// },
// 'fixedFeature': {
// 'add': api.addFixedFeatureLayer
// },
// 'loadFromServer': api.loadLayerFromServer,
// 'remove': api.removeLayer,
// 'removeFeature': function(layer, feature) {
// layer.getSource().removeFeature(feature)
// },
// 'updateWms': api.updateWmsLayer
// },
view: {
getExtent: this.transformResult(() => this.map_.getView().calculateExtent(), transformExtent),
fitExtent: this.transformInput(input => this.map_.getView().fit(input, {
padding: this.extentPadding_
}), transformExtent),
getCenter: this.transformResult(() => this.map_.getView().getCenter(), transform),
setCenter: this.transformInput(input => this.map_.getView().setCenter(input), transform),
getZoom: () => this.map_.getView().getZoom(),
setZoom: input => this.map_.getView().setZoom(input)
},
move: {
toExtent: this.moveToExtent.bind(this),
toPoint: this.moveToPoint.bind(this)
},
transform: {
coordinate: transform,
extent: transformExtent,
WKT: this.transformWKT.bind(this)
},
measure: {
length: this.measureAPI_.getLength.bind(this.measureAPI_),
area: this.measureAPI_.getArea.bind(this.measureAPI_),
distance: this.measureAPI_.getDistance.bind(this.measureAPI_)
},
popupModifier: {
register: (name, popupModifier) => this.map_.get('popupModifiers').register(name, popupModifier)
},
// 'style': {
// 'collection': styling.styleCollection,
// 'feature': styling.styleFeature,
// 'layer': styling.styleLayer
// },
version: this.getVersion.bind(this)
}
this.map_.api = Object.assign(this.map_.api || {}, api)
}
getVersion () {
return this.map_.get('guide4youVersion')
}
transformResult (func, transform) {
return options => {
let result = func()
if (options && options.projection) {
result = transform(result, this.map_.getView().getProjection(), options.projection)
}
return result
}
}
transformInput (func, transform) {
return (input, options = {}) => {
if (options.projection) {
input = transform(input, options.projection, this.map_.getView().getProjection())
}
return func(input)
}
}
moveToExtent (extent, options = {}) {
if (options.projection) {
extent = transformExtent(extent, options.projection, this.map_.getView().getProjection())
}
if (!options.padding) {
options.padding = this.extentPadding_
}
this.map_.get('move').toExtent(extent, options)
}
moveToPoint (coordinate, options = {}) {
if (options.projection) {
coordinate = transform(coordinate, options.projection, this.map_.getView().getProjection())
}
this.map_.get('move').toPoint(coordinate, options)
}
// zoomToExtent (extent, options) {
// if (options && options.projection) {
// extent = transformExtent(extent, options.projection, this.map_.getView().getProjection())
// }
// toPoint: this.zoomToPoint.bind(this)
// }
// ////////////// FEATURE MANIPULATION ////////////////
endFeatureManipulationInternal_ () {
if (this.featureManipulationInteraction_) {
this.featureManipulationInteraction_.setActive(false)
this.map_.removeInteraction(this.featureManipulationInteraction_)
}
$(this.map_.getViewport()).removeClass(cssClasses.crosshair)
$(this.map_.getViewport()).removeClass(cssClasses.arrow)
// and any other cursor changes
this.featureManipulationActive_ = false
this.dispatchEvent('endManipulation')
}
/**
* cancel the current feature manipulation
*/
cancelFeatureManipulation () {
if (this.featureManipulationActive_) {
this.endFeatureManipulationInternal_()
}
}
fitRectangle (coordinates, opt = {}) {
if (!opt.hasOwnProperty('projection')) {
opt.projection = 'EPSG:4326'
}
if (!opt.hasOwnProperty('constrainResolution')) {
opt.constrainResolution = false
}
if (!opt.hasOwnProperty('padding')) {
opt.padding = [0, 0, 0, 0]
}
if (getProj(opt.projection)) {
this.map_.getView().fit(
boundingExtent(
[
transform(
[parseFloat(coordinates[0][0]), parseFloat(coordinates[0][1])],
opt.projection,
this.map_.get('mapProjection').getCode()
),
transform(
[parseFloat(coordinates[1][0]), parseFloat(coordinates[1][1])],
opt.projection,
this.map_.get('mapProjection').getCode()
)
]
),
opt
)
} else {
Debug.error(`Unknown Projection '${opt.projection}'`)
}
}
setVisibleLayer (id) {
this.map_.getLayerGroup().recursiveForEach((layer) => {
if (layer.get('id') === id) {
layer.setVisible(true)
}
})
}
onKeyDown_ (e) {
if (this.featureManipulationActive_ && e.which === keyCodes.ESCAPE) {
this.endFeatureManipulationInternal_()
}
}
/**
* This function creates a layer from the given layerOptions and adds it the map
* @param {g4uLayerOptions} layerOptions
* @param {boolean} [atBottom=false] specifies if layer is added at bottom or top
* @returns {VectorLayer}
*/
addLayer (layerOptions, atBottom = false) {
const layer = this.layerConfigurator_.getFactory().createLayer(layerOptions)
if (!atBottom) {
this.map_.addLayer(layer)
} else {
this.map_.getLayers().insertAt(0, layer)
}
}
/**
* This function creates a layer from the given layerOptions, adds it as a VectorLayere and returns a promise which
* is resolved as soon as the layer is loaded fully.
* @param {g4uLayerOptions} layerOptions
* @returns {Promise.<VectorLayer>}
*/
loadLayerFromServer (layerOptions) {
layerOptions = layerOptions || {}
layerOptions.visible = true
layerOptions.source = layerOptions.source || {}
return new Promise((resolve, reject) => {
const layer = this.addLayer(layerOptions)
const source = layer.getSource()
const loadEndHandler = () => {
source.un('vectorloadend', loadErrorHandler)
resolve(layer)
}
const loadErrorHandler = () => {
source.un('vectorloaderror', loadEndHandler)
reject(new Error('vector load error'))
}
source.once('vectorloadend', loadEndHandler)
source.once('vectorloaderror', loadErrorHandler)
})
}
/**
* Creates a Feature from the given config
* @param {FeatureConfig} config
* @returns {ol.Feature}
*/
createFeature (config) {
return this.layerConfigurator_.getFactory().createFeature(config)
}
/**
* Removes a layer from the map
* @param {ol.layer.Base} layer
*/
removeLayer (layer) {
this.map_.getLayerGroup().removeLayer(layer)
}
/**
* Updates the layers parameter of a WMS Layer
* @param {string|number} layerId
* @param {string[]} layers
*/
updateWmsLayer (layerId, layers) {
this.map_.getLayerGroup().recursiveForEach(layer => {
if (layer.get('id') === layerId) {
layer.getSource().updateParams({
LAYERS: layers
})
}
})
}
/**
* Transforms WKT from one projection to another
*/
transformWKT (wkt, fromProj, toProj) {
const wktParser = new WKT()
return wktParser.writeGeometry(wktParser.readGeometry(wkt).transform(fromProj, toProj))
}
}