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 */ 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'] } /** * Takes a JSON object that contains a FeatureCollection and returns an array * of GeoJson objects. * * @static * @param {array} featureCollection - Array of GeoJSON objects that were contained in the feature collection. * @returns {array} Returns an array of geo json objects. * @memberof GeoJson */ 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 }) }) return list } /** * Validates and converts one set of coordinates of a specific type. * * @static * @param {string} type - Type of the GeoJson. * @param {array} coordinates - array of points. * @returns {array} * @memberof GeoJson */ static validateAndConvert(type, coordinates) { let converted = null if (!GeoJson.validateType(type)) throw new GeoJson.InvalidTypeError(type) else { if (GeoJson.validateCoordinates(type, coordinates)) { converted = GeoJson.convert(type, coordinates) } else { console.error( `Coordinates are invalid. They must be in format of type '${type} - ${GeoJson._getFormatStringOfType( type )}'` ) } } return converted } 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 {boolean} * @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 } } }