mongoose-geojson-schema
Version:
Schema definitions for GeoJSON types for use with Mongoose JS
496 lines (447 loc) • 13 kB
JavaScript
/**
* GeoJSON Schemas for Mongoose
*
* rough GeoJSON schemas for use with mongoose schema creation
*
* Based on GeoJSON Spec @ http://geojson.org/geojson-spec.html
*
* Created by Ben Dalton (ben@rideamigos) on 3/27/14.
* Copyright RideAmigos (http://rideamigos.com)
*
* */
let mongoose = require('mongoose')
let { Schema } = mongoose
var Types = mongoose.Types
var crs = {}
function validateCrs(crs) {
if (typeof crs !== 'object' && crs !== null) {
throw new mongoose.Error('Crs must be an object or null')
}
if (crs === null) {
return
}
if (!crs.type) {
throw new mongoose.Error('Crs must have a type')
}
if (crs.type !== 'name' && crs.type !== 'link') {
throw new mongoose.Error('Crs must be either a name or link')
}
if (!crs.properties) {
throw new mongoose.Error('Crs must contain a properties object')
}
if (crs.type === 'name' && !crs.properties.name) {
throw new mongoose.Error('Crs specified by name must have a name property')
}
if (
(crs.type === 'link' && !crs.properties.href) ||
(crs.type === 'link' && !crs.properties.type)
) {
throw new mongoose.Error(
'Crs specified by link must have a name and href property'
)
}
}
class GeoJSON extends mongoose.SchemaType {
static schemaName = 'GeoJSON'
constructor (key, options) {
super(key, options, 'GeoJSON')
}
cast (geojson) {
if (!geojson.type) {
throw new mongoose.Error('GeoJSON objects must have a type')
}
var TypeClass = mongoose.Schema.Types[geojson.type]
if (!TypeClass) {
throw new mongoose.Error(geojson.type + ' is not a valid GeoJSON type')
}
return TypeClass.prototype.cast.apply(this, arguments)
}
}
class Point extends mongoose.SchemaType {
static schemaName = 'Point'
constructor (key, options) {
super(key, options, 'Point')
}
cast (point) {
if (!point.type) {
throw new mongoose.Error('Point', point.type, 'point.type')
}
// type must be Point
if (point.type !== 'Point') {
throw new mongoose.Error(point.type + ' is not a valid GeoJSON type')
}
// check for crs
if (point.crs) {
crs = point.crs
validateCrs(crs)
} else {
crs = undefined
}
validatePoint(point.coordinates)
return point
}
}
function validatePoint(coordinates) {
// must be an array (object)
if (typeof coordinates !== 'object') {
throw new mongoose.Error('Point ' + coordinates + ' must be an array')
}
// must have 2/3 points
if (coordinates.length < 2 || coordinates.length > 3) {
throw new mongoose.Error(
'Point' + coordinates + ' must contain two or three coordinates'
)
}
// must have real numbers
if (isNaN(coordinates[0]) || isNaN(coordinates[1])) {
throw new mongoose.Error('Point must have real numbers')
}
// must have two numbers
if (
typeof coordinates[0] !== 'number' ||
typeof coordinates[1] !== 'number'
) {
throw new mongoose.Error('Point must have two numbers')
}
if (!crs) {
// longitude must be within bounds
if (coordinates[0] > 180 || coordinates[0] < -180) {
throw new mongoose.Error(
'Point' +
coordinates[0] +
' should be within the boundaries of longitude'
)
}
// latitude must be within bounds
if (coordinates[1] > 90 || coordinates[1] < -90) {
throw new mongoose.Error(
'Point' +
coordinates[1] +
' should be within the boundaries of latitude'
)
}
}
}
class MultiPoint extends mongoose.SchemaType {
static schemaName = 'MultiPoint'
constructor (key, options) {
super(key, options, 'MultiPoint')
}
cast (multipoint) {
// must be an array (object)
if (typeof multipoint.coordinates !== 'object') {
throw new mongoose.Error('MultiPoint must be an array')
}
if (!multipoint.type) {
throw new mongoose.Error('MultiPoint must have a type')
}
// type must be MultiPoint
if (multipoint.type !== 'MultiPoint') {
throw new mongoose.Error(multipoint.type + ' is not a valid GeoJSON type')
}
// check for crs
if (multipoint.crs) {
crs = multipoint.crs
validateCrs(crs)
}
validateMultiPoint(multipoint.coordinates)
return multipoint
}
}
function validateMultiPoint(coordinates) {
for (var i = 0; i < coordinates.length; i++) {
validatePoint(coordinates[i])
}
}
class LineString extends mongoose.SchemaType {
static schemaName = 'LineString'
constructor (key, options) {
super(key, options, 'LineString')
}
cast (linestring) {
if (!linestring.type) {
throw new mongoose.Error('LineString must have a type')
}
// type must be LineString
if (linestring.type !== 'LineString') {
throw new mongoose.Error(linestring.type + ' is not a valid GeoJSON type')
}
// must have at least two Points
if (linestring.coordinates.length < 2) {
throw new mongoose.Error('LineString type must have at least two Points')
}
// check for crs
if (linestring.crs) {
crs = linestring.crs
validateCrs(crs)
}
validateLineString(linestring.coordinates)
return linestring
}
}
function validateLineString(coordinates) {
for (var i = 0; i < coordinates.length; i++) {
validatePoint(coordinates[i])
}
}
class MultiLineString extends mongoose.SchemaType {
static schemaName = 'MultiLineString'
constructor (key, options) {
super(key, options, 'MultiLineString')
}
cast (multilinestring) {
// must be an array (object)
if (typeof multilinestring.coordinates !== 'object') {
throw new mongoose.Error('MultiLineString must be an array')
}
if (!multilinestring.type) {
throw new mongoose.Error(
'MultiLineString',
multilinestring.type + ' must have a type'
)
}
// type must be MultiLineString
if (multilinestring.type !== 'MultiLineString') {
throw new mongoose.Error(
multilinestring.type + ' is not a valid GeoJSON type'
)
}
validateMultiLineString(multilinestring.coordinates)
return multilinestring
}
}
function validateMultiLineString(coordinates) {
for (var i = 0; i < coordinates.length; i++) {
validateLineString(coordinates[i])
}
}
class Polygon extends mongoose.SchemaType {
static schemaName = 'Polygon'
constructor (key, options) {
super(key, options, 'Polygon')
}
cast (polygon) {
if (!polygon.type) {
throw new mongoose.Error('Polygon', polygon.type + ' must have a type')
}
// type must be Polygon
if (polygon.type !== 'Polygon') {
throw new mongoose.Error(polygon.type + ' is not a valid GeoJSON type')
}
// check for crs
if (polygon.crs) {
crs = polygon.crs
validateCrs(crs)
}
validatePolygon(polygon.coordinates)
return polygon
}
}
function arraysEqual(arr1, arr2) {
if (arr1.length !== arr2.length) return false
for (var i = arr1.length; i--; ) {
if (arr1[i] !== arr2[i]) return false
}
return true
}
function validatePolygon(coordinates) {
for (var i = 0; i < coordinates.length; i++) {
// The LinearRing elements must have at least four Points
if (coordinates[i].length < 4) {
throw new mongoose.Error(
'Each Polygon LinearRing must have at least four elements'
)
}
// the LinearRing objects must have identical start and end values
if (
!arraysEqual(coordinates[i][0], coordinates[i][coordinates[i].length - 1])
) {
throw new mongoose.Error(
'Each Polygon LinearRing must have an identical first and last point'
)
}
// otherwise the LinearRings must correspond to a LineString
validateLineString(coordinates[i])
}
}
class MultiPolygon extends mongoose.SchemaType {
static schemaName = 'MultiPolygon'
constructor (key, options) {
super(key, options, 'MultiPolygon')
}
cast (multipolygon) {
// must be an array (object)
if (typeof multipolygon.coordinates !== 'object') {
throw new mongoose.Error('MultiPolygon must be an array')
}
if (!multipolygon.type) {
throw new mongoose.Error('MultiPolygon must have a type')
}
// type must be Polygon
if (multipolygon.type !== 'MultiPolygon') {
throw new mongoose.Error(multipolygon.type + ' is not a valid GeoJSON type')
}
// check for crs
if (multipolygon.crs) {
crs = multipolygon.crs
validateCrs(crs)
}
validateMultiPolygon(multipolygon.coordinates)
return multipolygon
}
}
function validateMultiPolygon(coordinates) {
for (var i = 0; i < coordinates.length; i++) {
validatePolygon(coordinates[i])
}
}
class Geometry extends mongoose.SchemaType {
static schemaName = 'Geometry'
constructor (key, options) {
super(key, options, 'Geometry')
}
cast (geometry) {
// must be an array (object)
if (!geometry.type) {
throw new mongoose.Error('Geometry must must have a type')
}
// check for crs
if (geometry.crs) {
crs = geometry.crs
validateCrs(crs)
}
validateGeometry(geometry)
return geometry
}
}
function validateGeometry(geometry) {
switch (geometry.type) {
case 'Point':
validatePoint(geometry.coordinates)
break
case 'MultiPoint':
validateMultiPoint(geometry.coordinates)
break
case 'LineString':
validateLineString(geometry.coordinates)
break
case 'MultiLineString':
validateMultiLineString(geometry.coordinates)
break
case 'Polygon':
validatePolygon(geometry.coordinates)
break
case 'MultiPolygon':
validateMultiPolygon(geometry.coordinates)
break
default:
throw new mongoose.Error('Geometry must have a valid type')
}
}
class GeometryCollection extends mongoose.SchemaType {
static schemaName = 'GeometryCollection'
constructor (key, options) {
super(key, options, 'GeometryCollection')
}
cast (geometrycollection) {
// must be an array (object)
if (typeof geometrycollection.geometries !== 'object') {
throw new mongoose.Error('GeometryCollection must be an array')
}
// check for crs
if (geometrycollection.crs) {
crs = geometrycollection.crs
validateCrs(crs)
}
validateGeometries(geometrycollection.geometries)
return geometrycollection
}
}
function validateGeometries(geometries) {
for (var i = 0; i < geometries.length; i++) {
validateGeometry(geometries[i])
}
}
class Feature extends mongoose.SchemaType {
static schemaName = 'Feature'
constructor (key, options) {
super(key, options, 'Feature')
}
cast (feature) {
validateFeature(feature)
return feature
}
}
function validateFeature(feature) {
if (!feature.type) {
throw new mongoose.Error('Feature must have a type')
}
// type must be Feature
if (feature.type !== 'Feature') {
throw new mongoose.Error(feature.type + ' is not a valid GeoJSON type')
}
if (!feature.geometry) {
throw new mongoose.Error('Feature must have a geometry')
}
// check for crs
if (feature.crs) {
crs = feature.crs
validateCrs(crs)
}
validateGeometry(feature.geometry)
}
class FeatureCollection extends mongoose.SchemaType {
static schemaName = 'FeatureCollection'
constructor (key, options) {
super(key, options, 'FeatureCollection')
}
cast (featurecollection) {
if (!featurecollection.type) {
throw new mongoose.Error('FeatureCollection must have a type')
}
// type must be Polygon
if (featurecollection.type !== 'FeatureCollection') {
throw new mongoose.Error(
featurecollection.type + ' is not a valid GeoJSON type'
)
}
if (!featurecollection.features) {
throw new mongoose.Error('FeatureCollections must have a features object')
}
// check for crs
if (featurecollection.crs) {
crs = featurecollection.crs
validateCrs(crs)
}
validateFeatureCollection(featurecollection)
return featurecollection
}
}
function validateFeatureCollection(featurecollection) {
for (var i = 0; i < featurecollection.features.length; i++) {
validateFeature(featurecollection.features[i])
}
return featurecollection
}
Schema.Types.Feature = Feature
Schema.Types.FeatureCollection = FeatureCollection
Schema.Types.GeoJSON = GeoJSON
Schema.Types.Geometry = Geometry
Schema.Types.GeometryCollection = GeometryCollection
Schema.Types.LineString = LineString
Schema.Types.MultiLineString = MultiLineString
Schema.Types.MultiPoint = MultiPoint
Schema.Types.MultiPolygon = MultiPolygon
Schema.Types.Point = Point
Schema.Types.Polygon = Polygon
Types.Feature = Feature
Types.FeatureCollection = FeatureCollection
Types.GeoJSON = GeoJSON
Types.Geometry = Geometry
Types.GeometryCollection = GeometryCollection
Types.LineString = LineString
Types.MultiLineString = MultiLineString
Types.MultiPoint = MultiPoint
Types.MultiPolygon = MultiPolygon
Types.Point = Point
Types.Polygon = Polygon