|
| 1 | +(function(GeoJSON) { |
| 2 | + GeoJSON.version = '0.4.1'; |
| 3 | + |
| 4 | + // Allow user to specify default parameters |
| 5 | + GeoJSON.defaults = { |
| 6 | + doThrows: { |
| 7 | + invalidGeometry: false |
| 8 | + }, |
| 9 | + removeInvalidGeometries: false |
| 10 | + }; |
| 11 | + |
| 12 | + function InvalidGeometryError() { |
| 13 | + var args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; |
| 14 | + var item = args.shift(); |
| 15 | + var params = args.shift(); |
| 16 | + |
| 17 | + Error.apply(this, args); |
| 18 | + this.message = this.message || "Invalid Geometry: " + 'item: ' + JSON.stringify(item) + ', params: ' + JSON.stringify(params); |
| 19 | + } |
| 20 | + |
| 21 | + InvalidGeometryError.prototype = Error; |
| 22 | + |
| 23 | + |
| 24 | + GeoJSON.errors = { |
| 25 | + InvalidGeometryError: InvalidGeometryError |
| 26 | + }; |
| 27 | + |
| 28 | + //exposing so this can be overriden maybe by geojson-validation or the like |
| 29 | + GeoJSON.isGeometryValid = function(geometry){ |
| 30 | + if(!geometry || !Object.keys(geometry).length) |
| 31 | + return false; |
| 32 | + |
| 33 | + return !!geometry.type && !!geometry.coordinates && Array.isArray(geometry.coordinates) && !!geometry.coordinates.length; |
| 34 | + }; |
| 35 | + |
| 36 | + // The one and only public function. |
| 37 | + // Converts an array of objects into a GeoJSON feature collection |
| 38 | + GeoJSON.parse = function(objects, params, callback) { |
| 39 | + var geojson, |
| 40 | + settings = applyDefaults(params, this.defaults), |
| 41 | + propFunc; |
| 42 | + |
| 43 | + geomAttrs.length = 0; // Reset the list of geometry fields |
| 44 | + setGeom(settings); |
| 45 | + propFunc = getPropFunction(settings); |
| 46 | + |
| 47 | + if (Array.isArray(objects)) { |
| 48 | + geojson = {"type": "FeatureCollection", "features": []}; |
| 49 | + objects.forEach(function(item){ |
| 50 | + var feature = getFeature({item:item, params: settings, propFunc:propFunc}); |
| 51 | + if (settings.removeInvalidGeometries !== true || GeoJSON.isGeometryValid(feature.geometry)) { |
| 52 | + geojson.features.push(feature); |
| 53 | + } |
| 54 | + }); |
| 55 | + addOptionals(geojson, settings); |
| 56 | + } else { |
| 57 | + geojson = getFeature({item:objects, params: settings, propFunc:propFunc}); |
| 58 | + addOptionals(geojson, settings); |
| 59 | + } |
| 60 | + |
| 61 | + if (callback && typeof callback === 'function') { |
| 62 | + callback(geojson); |
| 63 | + } else { |
| 64 | + return geojson; |
| 65 | + } |
| 66 | + }; |
| 67 | + |
| 68 | + // Helper functions |
| 69 | + var geoms = ['Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeoJSON'], |
| 70 | + geomAttrs = []; |
| 71 | + |
| 72 | + // Adds default settings to user-specified params |
| 73 | + // Does not overwrite any settings--only adds defaults |
| 74 | + // the the user did not specify |
| 75 | + function applyDefaults(params, defaults) { |
| 76 | + var settings = params || {}; |
| 77 | + |
| 78 | + for(var setting in defaults) { |
| 79 | + if(defaults.hasOwnProperty(setting) && !settings[setting]) { |
| 80 | + settings[setting] = defaults[setting]; |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + return settings; |
| 85 | + } |
| 86 | + |
| 87 | + // Adds the optional GeoJSON properties crs and bbox |
| 88 | + // if they have been specified |
| 89 | + function addOptionals(geojson, settings){ |
| 90 | + if(settings.crs && checkCRS(settings.crs)) { |
| 91 | + if(settings.isPostgres) |
| 92 | + geojson.geometry.crs = settings.crs; |
| 93 | + else |
| 94 | + geojson.crs = settings.crs; |
| 95 | + } |
| 96 | + if (settings.bbox) { |
| 97 | + geojson.bbox = settings.bbox; |
| 98 | + } |
| 99 | + if (settings.extraGlobal) { |
| 100 | + geojson.properties = {}; |
| 101 | + for (var key in settings.extraGlobal) { |
| 102 | + geojson.properties[key] = settings.extraGlobal[key]; |
| 103 | + } |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + // Verify that the structure of CRS object is valid |
| 108 | + function checkCRS(crs) { |
| 109 | + if (crs.type === 'name') { |
| 110 | + if (crs.properties && crs.properties.name) { |
| 111 | + return true; |
| 112 | + } else { |
| 113 | + throw new Error('Invalid CRS. Properties must contain "name" key'); |
| 114 | + } |
| 115 | + } else if (crs.type === 'link') { |
| 116 | + if (crs.properties && crs.properties.href && crs.properties.type) { |
| 117 | + return true; |
| 118 | + } else { |
| 119 | + throw new Error('Invalid CRS. Properties must contain "href" and "type" key'); |
| 120 | + } |
| 121 | + } else { |
| 122 | + throw new Error('Invald CRS. Type attribute must be "name" or "link"'); |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + // Moves the user-specified geometry parameters |
| 127 | + // under the `geom` key in param for easier access |
| 128 | + function setGeom(params) { |
| 129 | + params.geom = {}; |
| 130 | + |
| 131 | + for(var param in params) { |
| 132 | + if(params.hasOwnProperty(param) && geoms.indexOf(param) !== -1){ |
| 133 | + params.geom[param] = params[param]; |
| 134 | + delete params[param]; |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + setGeomAttrList(params.geom); |
| 139 | + } |
| 140 | + |
| 141 | + // Adds fields which contain geometry data |
| 142 | + // to geomAttrs. This list is used when adding |
| 143 | + // properties to the features so that no geometry |
| 144 | + // fields are added the properties key |
| 145 | + function setGeomAttrList(params) { |
| 146 | + for(var param in params) { |
| 147 | + if(params.hasOwnProperty(param)) { |
| 148 | + if(typeof params[param] === 'string') { |
| 149 | + geomAttrs.push(params[param]); |
| 150 | + } else if (typeof params[param] === 'object') { // Array of coordinates for Point |
| 151 | + geomAttrs.push(params[param][0]); |
| 152 | + geomAttrs.push(params[param][1]); |
| 153 | + } |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + if(geomAttrs.length === 0) { throw new Error('No geometry attributes specified'); } |
| 158 | + } |
| 159 | + |
| 160 | + // Creates a feature object to be added |
| 161 | + // to the GeoJSON features array |
| 162 | + function getFeature(args) { |
| 163 | + var item = args.item, |
| 164 | + params = args.params, |
| 165 | + propFunc = args.propFunc; |
| 166 | + |
| 167 | + var feature = { "type": "Feature" }; |
| 168 | + |
| 169 | + feature.geometry = buildGeom(item, params); |
| 170 | + feature.properties = propFunc.call(item); |
| 171 | + |
| 172 | + return feature; |
| 173 | + } |
| 174 | + |
| 175 | + function isNested(val){ |
| 176 | + return (/^.+\..+$/.test(val)); |
| 177 | + } |
| 178 | + |
| 179 | + // Assembles the `geometry` property |
| 180 | + // for the feature output |
| 181 | + function buildGeom(item, params) { |
| 182 | + var geom, |
| 183 | + attr; |
| 184 | + |
| 185 | + for(var gtype in params.geom) { |
| 186 | + var val = params.geom[gtype]; |
| 187 | + var coordinates = []; |
| 188 | + var itemClone; |
| 189 | + var paths; |
| 190 | + // If we've already found a matching geometry, stop the loop. |
| 191 | + if (geom !== undefined && geom !== false) { |
| 192 | + break; |
| 193 | + } |
| 194 | + |
| 195 | + // Geometry parameter specified as: {Point: 'coords'} |
| 196 | + if(typeof val === 'string' && item.hasOwnProperty(val)) { |
| 197 | + if(gtype === 'GeoJSON') { |
| 198 | + geom = item[val]; |
| 199 | + } else { |
| 200 | + geom = { |
| 201 | + type: gtype, |
| 202 | + coordinates: item[val] |
| 203 | + }; |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + // Geometry parameter specified as: {Point: 'geo.coords'} |
| 208 | + else if(typeof val === 'string' && isNested(val)) { |
| 209 | + geom = undefined; |
| 210 | + paths = val.split('.'); |
| 211 | + itemClone = item; |
| 212 | + for (var m = 0; m < paths.length; m++) { |
| 213 | + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[m])) { |
| 214 | + m = paths.length; |
| 215 | + geom = false; |
| 216 | + } else { |
| 217 | + itemClone = itemClone[paths[m]]; // Iterate deeper into the object |
| 218 | + } |
| 219 | + } |
| 220 | + if (geom !== false) { |
| 221 | + geom = { |
| 222 | + type: gtype, |
| 223 | + coordinates: itemClone |
| 224 | + }; |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + /* Handle things like: |
| 229 | + Polygon: { |
| 230 | + northeast: ['lat', 'lng'], |
| 231 | + southwest: ['lat', 'lng'] |
| 232 | + } |
| 233 | + */ |
| 234 | + else if(typeof val === 'object' && !Array.isArray(val)) { |
| 235 | + /*jshint loopfunc: true */ |
| 236 | + var points = Object.keys(val).map(function(key){ |
| 237 | + var order = val[key]; |
| 238 | + var newItem = item[key]; |
| 239 | + return buildGeom(newItem, {geom:{ Point: order}}); |
| 240 | + }); |
| 241 | + geom = { |
| 242 | + type: gtype, |
| 243 | + /*jshint loopfunc: true */ |
| 244 | + coordinates: [].concat(points.map(function(p){ |
| 245 | + return p.coordinates; |
| 246 | + })) |
| 247 | + }; |
| 248 | + } |
| 249 | + |
| 250 | + // Geometry parameter specified as: {Point: ['lat', 'lng', 'alt']} |
| 251 | + else if(Array.isArray(val) && item.hasOwnProperty(val[0]) && item.hasOwnProperty(val[1]) && item.hasOwnProperty(val[2])){ |
| 252 | + geom = { |
| 253 | + type: gtype, |
| 254 | + coordinates: [ Number(item[val[1]]), Number(item[val[0]]), Number(item[val[2]]) ] |
| 255 | + }; |
| 256 | + } |
| 257 | + |
| 258 | + // Geometry parameter specified as: {Point: ['lat', 'lng']} |
| 259 | + else if(Array.isArray(val) && item.hasOwnProperty(val[0]) && item.hasOwnProperty(val[1])){ |
| 260 | + geom = { |
| 261 | + type: gtype, |
| 262 | + coordinates: [Number(item[val[1]]), Number(item[val[0]])] |
| 263 | + }; |
| 264 | + } |
| 265 | + |
| 266 | + // Geometry parameter specified as: {Point: ['container.lat', 'container.lng', 'container.alt']} |
| 267 | + else if(Array.isArray(val) && isNested(val[0]) && isNested(val[1]) && isNested(val[2])){ |
| 268 | + geom = undefined; |
| 269 | + for (var i = 0; i < val.length; i++) { // i.e. 0 and 1 |
| 270 | + paths = val[i].split('.'); |
| 271 | + itemClone = item; |
| 272 | + for (var j = 0; j < paths.length; j++) { |
| 273 | + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[j])) { |
| 274 | + i = val.length; |
| 275 | + j = paths.length; |
| 276 | + geom = false; |
| 277 | + } else { |
| 278 | + itemClone = itemClone[paths[j]]; // Iterate deeper into the object |
| 279 | + } |
| 280 | + } |
| 281 | + coordinates[i] = itemClone; |
| 282 | + } |
| 283 | + if (geom !== false) { |
| 284 | + geom = { |
| 285 | + type: gtype, |
| 286 | + coordinates: [Number(coordinates[1]), Number(coordinates[0]), Number(coordinates[2])] |
| 287 | + }; |
| 288 | + } |
| 289 | + } |
| 290 | + |
| 291 | + // Geometry parameter specified as: {Point: ['container.lat', 'container.lng']} |
| 292 | + else if(Array.isArray(val) && isNested(val[0]) && isNested(val[1])){ |
| 293 | + for (var k = 0; k < val.length; k++) { // i.e. 0 and 1 |
| 294 | + paths = val[k].split('.'); |
| 295 | + itemClone = item; |
| 296 | + for (var l = 0; l < paths.length; l++) { |
| 297 | + if (itemClone == undefined || !itemClone.hasOwnProperty(paths[l])) { |
| 298 | + k = val.length; |
| 299 | + l = paths.length; |
| 300 | + geom = false; |
| 301 | + } else { |
| 302 | + itemClone = itemClone[paths[l]]; // Iterate deeper into the object |
| 303 | + } |
| 304 | + } |
| 305 | + coordinates[k] = itemClone; |
| 306 | + } |
| 307 | + if (geom !== false) { |
| 308 | + geom = { |
| 309 | + type: gtype, |
| 310 | + coordinates: [Number(coordinates[1]), Number(coordinates[0])] |
| 311 | + }; |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + // Geometry parameter specified as: {Point: [{coordinates: [lat, lng]}]} |
| 316 | + else if (Array.isArray(val) && val[0].constructor.name === 'Object' && Object.keys(val[0])[0] === 'coordinates'){ |
| 317 | + geom = { |
| 318 | + type: gtype, |
| 319 | + coordinates: [Number(item.coordinates[(val[0].coordinates).indexOf('lng')]), Number(item.coordinates[(val[0].coordinates).indexOf('lat')])] |
| 320 | + }; |
| 321 | + } |
| 322 | + } |
| 323 | + |
| 324 | + if(params.doThrows && params.doThrows.invalidGeometry && !GeoJSON.isGeometryValid(geom)){ |
| 325 | + throw new InvalidGeometryError(item, params); |
| 326 | + } |
| 327 | + |
| 328 | + return geom; |
| 329 | + } |
| 330 | + |
| 331 | + // Returns the function to be used to |
| 332 | + // build the properties object for each feature |
| 333 | + function getPropFunction(params) { |
| 334 | + var func; |
| 335 | + |
| 336 | + if(!params.exclude && !params.include) { |
| 337 | + func = function(properties) { |
| 338 | + for(var attr in this) { |
| 339 | + if(this.hasOwnProperty(attr) && (geomAttrs.indexOf(attr) === -1)) { |
| 340 | + properties[attr] = this[attr]; |
| 341 | + } |
| 342 | + } |
| 343 | + }; |
| 344 | + } else if(params.include) { |
| 345 | + func = function(properties) { |
| 346 | + params.include.forEach(function(attr){ |
| 347 | + properties[attr] = this[attr]; |
| 348 | + }, this); |
| 349 | + }; |
| 350 | + } else if(params.exclude) { |
| 351 | + func = function(properties) { |
| 352 | + for(var attr in this) { |
| 353 | + if(this.hasOwnProperty(attr) && (geomAttrs.indexOf(attr) === -1) && (params.exclude.indexOf(attr) === -1)) { |
| 354 | + properties[attr] = this[attr]; |
| 355 | + } |
| 356 | + } |
| 357 | + }; |
| 358 | + } |
| 359 | + |
| 360 | + return function() { |
| 361 | + var properties = {}; |
| 362 | + |
| 363 | + func.call(this, properties); |
| 364 | + |
| 365 | + if(params.extra) { addExtra(properties, params.extra); } |
| 366 | + return properties; |
| 367 | + }; |
| 368 | + } |
| 369 | + |
| 370 | + // Adds data contained in the `extra` |
| 371 | + // parameter if it has been specified |
| 372 | + function addExtra(properties, extra) { |
| 373 | + for(var key in extra){ |
| 374 | + if(extra.hasOwnProperty(key)) { |
| 375 | + properties[key] = extra[key]; |
| 376 | + } |
| 377 | + } |
| 378 | + |
| 379 | + return properties; |
| 380 | + } |
| 381 | + |
| 382 | +}(typeof module == 'object' ? module.exports : window.GeoJSON = {})); |
0 commit comments