A p2p database for geojson map data (based on osm-p2p-db)
geojson-p2p-db shares a lot of the same features as osm-p2p-db, including replication, forking, append-only logs, and changesets. But geojson-p2p-db differs from osm-p2p-db in several ways.
-
OpenStreetMap (and osm-p2p-db) stores spatial data as nodes, ways, and relations. GeoJSON stores spatial data as Points, LineStrings, Polygons with Multi modifiers for each (MultiPoint, MultiLinestring, MultiPolygon).
-
Nodes in osm-p2p-db are used to maintain topology with other features. There is no concept of topology in geojson-p2p-db.
-
GeoJSON stores addtional metadata in a
propertiesobject for each Feature. OSM and osm-p2p-db generally usetags.
Every spatial entry in geojson-p2p-db is stored as a GeoJSON Feature with geometry and properties objects. Each Feature or Geometry type in a FeatureCollection or GeometryCollection must be stored in separate entries in geojson-p2p-db. It is not possible to store a FeatureCollection or GeometryCollection as a single entry in geojson-p2p-db. This makes storing spatial indexes and streaming much easier. It's sort of similar to the idea of Newline Delimited JSON.
var hyperlog = require('hyperlog')
var level = require('level')
var db = {
log: level('/tmp/geojson-p2p/log'),
index: level('/tmp/geojson-p2p/index')
}
var fdstore = require('fd-chunk-store')
var storefile = '/tmp/geojson-p2p/kdb'
var gjdb = require('../')
var gj = gjdb({
log: hyperlog(db.log, { valueEncoding: 'json' }),
db: db.index,
store: fdstore(4096, storefile)
})
if (process.argv[2] === 'create') {
var value = JSON.parse(process.argv[3])
gj.create(value, function (err, key, node) {
if (err) console.error(err)
else console.log(key)
})
} else if (process.argv[2] === 'query') {
var q = process.argv.slice(3).map(csplit)
gj.query(q, function (err, pts) {
if (err) console.error(err)
else pts.forEach(function (pt) {
console.log(pt)
})
})
}
function csplit (x) { return x.split(',').map(Number) }Now we can add a few GeoJSON features and search within a bounding box
$ mkdir /tmp/geojson-p2p
$ node db.js create '{"type":"Feature","id":"5155edcbb6f3e2a4","properties":{"i-am":"a-point"},"geometry":{"type":"Point","coordinates":[-123.027648,48.695492]}}'
3b3842791d71c865
$ node db.js create '{"type":"Feature","id":"a073bf01dfca3ee3","properties":{"i-am":"a-line"},"geometry":{"type":"LineString","coordinates":[[-123.147125,48.522062],[-123.063354,48.582966],[-122.994689,48.489306],[-122.86972,48.568429]]}}'
0f16be2e09c144f4
$ node db.js query 48.672486,48.726529,-123.075371,-122.997093
{
"type": "Feature",
"id": "3b3842791d71c865",
"properties": {
"i-am": "a-point"
},
"geometry": {
"type": "Point",
"coordinates": [
-123.027648,
48.695492
]
},
"version": "74731c8381df7648eb8709e1d272240643c339a6f270420fc53c1ad23040228b"
}
Create a new geojson-p2p database
Parameters
optsObject
Examples
var hyperlog = require('hyperlog')
var level = require('level')
var fdstore = require('fd-chunk-store')
var gjdb = require('geojson-p2p-db')
var gj = gjdb({
log: hyperlog(level('log'), { valueEncoding: 'json' }),
db: level('index'),
store: fdstore(4096, '/tmp/geojson-p2p/kdb')
})Store a new geojson feature or changeset from value. cb(err, id, node)
is returned with the generated id and the node from the underlying
hyperlog.
Parameters
valueObject A GeoJSON Feature object containinggeometry,propertiesandchangesetproperties or a changeset object with atype='changeset'andtagsproperties.tags.commentis recommended for storing text describing the changeset. Note: The GeoJSON specification allows anidproperty on Feature objects. This property will be added or destructively overwritten in the geojson-p2p-db to ensure uniqueness.optsObject Options to pass to hyperkv. (optional, default{})cbFunction A callback function with the parameterserr,id,nodewhereidis the generated id and thenodefrom the underlying hyperlog.
Examples
gj.create({ type: 'changeset', tags: { comment: 'This is a new changeset' }},
function (err, id, node) {
if (err) throw err
var feat = {
type: "Feature",
properties: {
beep: 'boop'
},
geometry: {
type: 'Point',
coordinates: [-123.027648, 48.695492]
},
changeset: id
}
gj.create(feat, function (err, id, node) {
if (err) console.error(err)
console.log('Id', id)
console.log('Node', node)
})
})Replace a document key from value. The document will be created if it does
not exist. cb(err, node) is returned with the node from the underlying
hyperlog.
Parameters
keystring Id of a document to replace withvalue.valueObject A GeoJSON Feature object containinggeometry,propertiesandchangesetproperties or a changeset object with atype='changeset'andtagsproperties.tags.commentis recommended for storing text describing the changeset. Note: The GeoJSON specification allows anidproperty on Feature objects. This property will be added or destructively overwritten in the geojson-p2p-db to ensure uniqueness.optsObject Options to pass to hyperkv. (optional, default{})cbFunction A callback function with the parameterserr,nodewith thenodefrom the underlying hyperlog.
Examples
gj.create({ type: 'changeset', tags: { comment: 'This is a new changeset' }},
function (err, id, node) {
if (err) throw err
var feat = {
type: "Feature",
properties: {
beep: 'boop'
},
geometry: {
type: 'Point',
coordinates: [-123.027648, 48.695492]
},
changeset: id
}
gj.create(feat, function (err, id, node) {
if (err) console.error(err)
console.log('Id', id)
console.log('Node', node)
feat.properties = {
boop: 'beep'
}
feat.changeset = id
gj.put(id, feat, function (err, node) {
console.log('New node', node)
})
})
})Mark the document at key as deleted. cb(err, node) is returned with the
node from the underlying hyperlog.
Parameters
keystring Id of the document to mark as deleted.optsObject Options to pass to hyperkv. (optional, default{})cbFunction A callback function with the parameterserr,nodewith thenodefrom the underlying hyperlog.
Examples
gj.create({ type: 'changeset', tags: { comment: 'This is a new changeset' }},
function (err, id, node) {
if (err) throw err
var feat = {
type: "Feature",
properties: {
beep: 'boop'
},
geometry: {
type: 'Point',
coordinates: [-123.027648, 48.695492]
},
changeset: id
}
gj.create(feat, function (err, id, node) {
if (err) console.error(err)
console.log('Id', id)
console.log('Node', node)
gj.del(id, function (err, node) {
if (err) throw err
gj.get(id, function (err, node) {
if (err) throw err
console.log('Deleted', node)
})
})
})
})Atomically put or delete an array of documents as rows
Parameters
rowsArray<Object> Array ofrowto put or delete.optsObject Options to pass to hyperkv (optional, default{})cbFunction A callback function with the parameterserr,nodeswith thenodesfrom the underlying hyperlog.
Examples
// With existing changeset id of 'A'
var rows = [
{ type: 'put', value: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-122.85, 48.52] }, changeset: 'A' } },
{ type: 'put', value: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-122.90, 48.60] }, changeset: 'A' } }
]
gj.batch(rows, function (err, nodes) {
if (err) throw err
console.log(nodes)
})Get the documents with the id key.
Parameters
keystring The id of the documents to retrieve.optsObject Options to pass to hyperkv. (optional, default{})cbFunction A callback function with the parameterserr,docswheredocsis an object mapping hyperlog hashes to current document values. If a document has been deleted, it will only have the properties{ id: <id>, version: <version>, deleted: true }.
Examples
gj.create({ type: 'changeset', tags: { comment: 'This is a new changeset' } },
function (err, id, node) {
if (err) throw err
var feat = {
type: "Feature",
properties: {
beep: 'boop'
},
geometry: {
type: 'Point',
coordinates: [-123.027648, 48.695492]
},
changeset: id
}
gj.create(feat, function (err, id, node) {
if (err) console.error(err)
console.log('Id', id)
gj.get(id, function (err, nodes) {
if (err) throw err
console.log('Nodes', nodes)
})
})
})Query the database using latitude/longitude bounds.
Parameters
qArray<Array<Number>> An array of[[ minLat, maxLat], [minLng, maxLng]]coordinate pairs to specify a bounding box.optsObject Options to pass to kdb-tree-store query. (optional, default{})cbFunction A callback function with the parameterserr, reswhereresis an array of documents each containing anidproperty and aversionproperty which is the hash key from the underlying hyperlog. Deleted documents will only have the properties{ id: <id>, version: <version>, deleted: true }.
Examples
// With existing changeset id of 'A'
var rows = [
{ type: 'put', value: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-122.85, 48.52] }, changeset: 'A' } },
{ type: 'put', value: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-122.90, 48.70] }, changeset: 'A' } }
]
gj.batch(rows, function (err, nodes) {
if (err) throw err
console.log('Created', nodes)
gj.query([ [ 48.50, 48.60 ], [ -122.89, -122.80 ] ], function (err, res) {
if (err) throw err
console.log('Results', res)
})
})Return a readable object stream of query results contained in the
query q.
Parameters
qArray<Array<Number>> An array of[[ minLat, maxLat], [minLng, maxLng]]coordinate pairs to specify a bounding box.optsObject Options to pass to kdb-tree-store query. (optional, default{})
Get a list of document version ids in a changeset by the changeset id key
Parameters
keystring Id of changeset.optscbFunction? An optional callback function with the parameterserr,versionswhereversionsis an array of changeset ids. If no callback is specified, a readable object stream is returned.
Examples
gj.create({ type: 'changeset', tags: { comment: 'This is a new changeset' }},
function (err, changesetId, node) {
if (err) throw err
var rows = [
{ type: 'put', value: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-122.85, 48.52] }, changeset: changesetId } },
{ type: 'put', value: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-122.90, 48.60] }, changeset: changesetId } }
]
gj.batch(rows, function (err, nodes) {
if (err) throw err
gj.getChanges(changesetId, function (err, nodes) {
if (err) throw err
console.log(nodes)
})
})
})Copyright 2017 Nick Peihl
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.