330 lines
11 KiB
JavaScript
330 lines
11 KiB
JavaScript
|
import { GeoGraphics, GeoShape, GeoMultiShape, GeoLine, GeoPoint } from './geographics.js'
|
||
|
|
||
|
/**
|
||
|
* Helper class for handling GeoJson data.
|
||
|
* As specified by [RFC7946](https://tools.ietf.org/html/rfc7946).
|
||
|
*
|
||
|
* @static
|
||
|
* @export
|
||
|
* @class GeoJsonGraphics
|
||
|
* @extends {GeoGraphics}
|
||
|
*/
|
||
|
export default class GeoJson {
|
||
|
static isLineType(type) {
|
||
|
return type == 'LineString' || type == 'MultiLineString'
|
||
|
}
|
||
|
|
||
|
static _getFormatStringOfType(type) {
|
||
|
let description = ', where p represents a coordinate point'
|
||
|
let format = ''
|
||
|
switch (type) {
|
||
|
case 'Point':
|
||
|
format = 'p'
|
||
|
break
|
||
|
case 'LineString':
|
||
|
format = '[p1,p2,p3,...,pn]'
|
||
|
break
|
||
|
case 'Polygon':
|
||
|
format = '[ [p1,p2,...,pn], [h1,h2,...,hn] ]'
|
||
|
description += ' and h also represents a coordinate point, but it creates a hole.'
|
||
|
break
|
||
|
default:
|
||
|
format = type
|
||
|
description = " is either not valid or not yet implemented in method '_getFormatStringOfType(type)'."
|
||
|
}
|
||
|
|
||
|
return format + ' - ' + description
|
||
|
}
|
||
|
|
||
|
static get types() {
|
||
|
return ['Point', 'LineString', 'Polygon', 'MultiPoint', 'MultiLineString', 'MultiPolygon']
|
||
|
}
|
||
|
|
||
|
static unwrapFeatureCollection(featureCollection) {
|
||
|
if (featureCollection.features == null) {
|
||
|
console.error(
|
||
|
'Error at GeoJson.unrwapFeatureCollection(collection): Provided object was no valid FeatureCollection.',
|
||
|
featureCollection
|
||
|
)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
let list = []
|
||
|
|
||
|
featureCollection.features.forEach(feature => {
|
||
|
let { type, coordinates } = feature
|
||
|
|
||
|
if (feature.type.toLowerCase() == 'feature') {
|
||
|
;({ type, coordinates } = feature.geometry)
|
||
|
}
|
||
|
|
||
|
list.push({ type, coordinates })
|
||
|
// console.log({type, coordinates})
|
||
|
})
|
||
|
|
||
|
return list
|
||
|
}
|
||
|
|
||
|
static validateAndConvert(type, coordinates) {
|
||
|
if (!GeoJson.validateType(type)) throw new GeoJson.InvalidTypeError(type)
|
||
|
else {
|
||
|
if (GeoJson.validateCoordinates(type, coordinates)) {
|
||
|
let converted = GeoJson.convert(type, coordinates)
|
||
|
return converted
|
||
|
} else {
|
||
|
console.error(
|
||
|
`Coordinates are invalid. They must be in format of type '${type} - ${GeoJson._getFormatStringOfType(
|
||
|
type
|
||
|
)}'`
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static validateType(type) {
|
||
|
return GeoJson.types.indexOf(type) != -1
|
||
|
}
|
||
|
|
||
|
static validateCoordinates(type, coordinates) {
|
||
|
let valid = false
|
||
|
|
||
|
switch (type) {
|
||
|
case 'Point':
|
||
|
valid = !!GeoJson.validateAndConvertPoint(coordinates)
|
||
|
break
|
||
|
case 'LineString':
|
||
|
valid = GeoJson.validateLineString(coordinates)
|
||
|
break
|
||
|
case 'Polygon':
|
||
|
valid = GeoJson.validatePolygon(coordinates)
|
||
|
break
|
||
|
case 'MultiPolygon':
|
||
|
valid = true
|
||
|
for (let i = 0; i < coordinates.length; i++) {
|
||
|
if (!GeoJson.validatePolygon(coordinates[i])) {
|
||
|
valid = false
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
break
|
||
|
case 'MultiPoint':
|
||
|
case 'MultiLineString':
|
||
|
default:
|
||
|
console.error('Type was not yet implemented: ', type)
|
||
|
}
|
||
|
return valid
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validates a point if it's an valid coordinate.
|
||
|
*
|
||
|
* NOTE: Here we are not following the GeoJSON standard.
|
||
|
* For convenience multiple forms of representing a coordinate are
|
||
|
* considered valid. A complete list is provided in the GeoUtils.
|
||
|
*
|
||
|
* @param {object} point - The point that is tested for validity.
|
||
|
* @returns
|
||
|
* @memberof GeoJson
|
||
|
*/
|
||
|
static validateAndConvertPoint(point) {
|
||
|
return GeoUtils.validateCoordinate(point)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*Validates if the given points represent a 'LineString'.
|
||
|
*
|
||
|
* @param {array} points - A list of coordinates that represent a line.
|
||
|
* @returns {boolean} - Returns true, if the array is in formes as: [x1,x2,x3,...,xn]. Where x# represent a valid coordinate.
|
||
|
* @memberof GeoJson
|
||
|
*/
|
||
|
static validateLineString(points) {
|
||
|
let valid = false
|
||
|
if (Array.isArray(points)) valid = points.every(GeoJson.validateAndConvertPoint)
|
||
|
return valid
|
||
|
}
|
||
|
|
||
|
static validatePolygon(points) {
|
||
|
let valid = false
|
||
|
if ((Array.isArray(points) && points.length >= 1) || points.length <= 2)
|
||
|
valid = points.every(this.validateLineString)
|
||
|
return valid
|
||
|
}
|
||
|
|
||
|
static convert(type, coordinates) {
|
||
|
let converted = null
|
||
|
switch (type) {
|
||
|
case 'Point':
|
||
|
converted = GeoJson.validateAndConvertPoint(coordinates)
|
||
|
break
|
||
|
case 'LineString':
|
||
|
converted = GeoJson._convertLineString(coordinates)
|
||
|
break
|
||
|
case 'Polygon':
|
||
|
converted = GeoJson._convertPolygon(coordinates)
|
||
|
break
|
||
|
case 'MultiPolygon':
|
||
|
converted = GeoJson._convertMultiPolygon(coordinates)
|
||
|
break
|
||
|
default:
|
||
|
throw new GeoJson.InvalidTypeError(type)
|
||
|
}
|
||
|
|
||
|
return converted
|
||
|
}
|
||
|
|
||
|
static _convertLineString(coordinates) {
|
||
|
return coordinates.map(point => {
|
||
|
return GeoJson.validateAndConvertPoint(point)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
static _convertPolygon(coordinates) {
|
||
|
return coordinates.map(shape => {
|
||
|
return GeoJson._convertLineString(shape)
|
||
|
})
|
||
|
}
|
||
|
static _convertMultiPolygon(coordinates) {
|
||
|
return coordinates.map(polygon => {
|
||
|
return GeoJson._convertPolygon(polygon)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
GeoJson.InvalidTypeError = class extends Error {
|
||
|
constructor(type) {
|
||
|
super(`The requested Type was not implemented: ${type}.`)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* GeoUtils contains a collection of useful functions when working with maps.
|
||
|
*
|
||
|
* @static
|
||
|
*/
|
||
|
export class GeoUtils {
|
||
|
static transformToGeoGraphics(list) {
|
||
|
let geographicsList = []
|
||
|
list.forEach(item => {
|
||
|
if (item.type == 'FeatureCollection') {
|
||
|
item.features.forEach(feature => {
|
||
|
let { type, coordinates } = feature
|
||
|
|
||
|
if (type.toLowerCase() == 'feature') ({ type, coordinates } = feature.geometry)
|
||
|
|
||
|
coordinates = GeoJson.validateAndConvert(type, coordinates)
|
||
|
|
||
|
let geographics = this.fromGeoJsonToGeoGraphics(type, coordinates)
|
||
|
if (geographics) geographicsList.push(geographics)
|
||
|
})
|
||
|
} else {
|
||
|
let geo = fromGeoJsonToGeoGraphics(item.type, item.geometry)
|
||
|
if (geo) geographicsList.push(geo)
|
||
|
}
|
||
|
})
|
||
|
return geographicsList
|
||
|
}
|
||
|
|
||
|
static resolveFeatureCollection(collection) {
|
||
|
if (!collection.features) {
|
||
|
console.error(
|
||
|
'Error in GeoUtils.resolveFeatureCollection(colelction): Passed parameter was no feature collection.',
|
||
|
collection
|
||
|
)
|
||
|
return
|
||
|
}
|
||
|
let geojson = []
|
||
|
collection.features.forEach(feature => {
|
||
|
let { type, coordinates } = feature
|
||
|
|
||
|
if (feature.type == feature) {
|
||
|
coordinates = feature.geometry.coordinates
|
||
|
type = feature.geometry.type
|
||
|
}
|
||
|
|
||
|
geojson.push({ type, coordinates })
|
||
|
})
|
||
|
|
||
|
return geojson
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates the appropriate GeoGraphics object from a GeoJson type.
|
||
|
* The coordinates need to be alread in the appropriate PIXI format.
|
||
|
* If not already - this can be achieved by calling 'GeoJson.validateAndConvert(type, points)'
|
||
|
* beforehand.
|
||
|
*
|
||
|
* @static
|
||
|
* @param {string} type - Any of the GeoJson types ('Point', 'LineString', 'Polygon', 'MultiPoint', 'MultiLineString', 'MultiPolygon').
|
||
|
* @param {array} coordinates - Array of coordinates that fit the type. The positions within these must be in PIXI format: {x:a, y:b}.
|
||
|
* @returns {GeoGraphics} - Returns a GeoGraphics object. If the conversion fails, it returns null.
|
||
|
* @memberof GeoGraphics
|
||
|
*/
|
||
|
static fromGeoJsonToGeoGraphics(type, coordinates, opts = {}) {
|
||
|
let geographics = null
|
||
|
|
||
|
/**
|
||
|
* TODO: REMOVE
|
||
|
* Just for initial debugging purposes
|
||
|
*/
|
||
|
Object.assign(opts, {
|
||
|
debug: true
|
||
|
})
|
||
|
|
||
|
switch (type) {
|
||
|
case 'Polygon':
|
||
|
geographics = new GeoShape(coordinates, opts)
|
||
|
break
|
||
|
|
||
|
case 'MultiPolygon':
|
||
|
geographics = new GeoMultiShape(coordinates, opts)
|
||
|
break
|
||
|
case 'LineString':
|
||
|
geographics = new GeoLine(coordinates, opts)
|
||
|
break
|
||
|
case 'Point':
|
||
|
geographics = new GeoPoint(coordinates, opts)
|
||
|
break
|
||
|
default:
|
||
|
console.log('Could not create Geographics for type: ' + type + '. This was not implemented yet.')
|
||
|
//Nothing
|
||
|
}
|
||
|
|
||
|
return geographics
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validates an object, if it's an coordinate object.
|
||
|
* Coordinate objects can be in the following forms:
|
||
|
*
|
||
|
* [lng, lat] - GeoJSON array format. !Attention lng and lat are swapped compared to the x,y format.
|
||
|
* {x: lat, y: lng} - 'correct' PIXI.format
|
||
|
* {lat, lng}
|
||
|
* {latitude: lat, longitude: lng}
|
||
|
*
|
||
|
* @static
|
||
|
* @param {object / array} coordinate - Coordinate to be tested, if it is an valid coordinate.
|
||
|
* @returns - Returns the coordinate properly transformed. If transformation was not possible, it returns null.
|
||
|
* @memberof GeoGraphics
|
||
|
*/
|
||
|
static validateCoordinate(coordinate) {
|
||
|
if (Array.isArray(coordinate)) {
|
||
|
if (coordinate.length == 2 && typeof coordinate[0] == 'number' && typeof coordinate[1] == 'number')
|
||
|
return new PIXI.Point(coordinate[1], coordinate[0])
|
||
|
else return false
|
||
|
} else {
|
||
|
const latvalues = ['x', 'lat', 'latitude']
|
||
|
const lngvalues = ['y', 'lng', 'longitude']
|
||
|
|
||
|
let result = {}
|
||
|
for (let key of Object.keys(coordinate)) {
|
||
|
let target = key.toLowerCase()
|
||
|
if (latvalues.indexOf(target) !== -1) result.x = coordinate[key]
|
||
|
else if (lngvalues.indexOf(target) !== -1) result.y = coordinate[key]
|
||
|
}
|
||
|
|
||
|
if (result.hasOwnProperty('x') && result.hasOwnProperty('y')) return new PIXI.Point(result.x, result.y)
|
||
|
else return false
|
||
|
}
|
||
|
}
|
||
|
}
|