Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ geojson-vt.js
node_modules
coverage
.nyc_output
.idea
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ var tileIndex = geojsonvt(data, {
lineMetrics: false, // whether to enable line metrics tracking for LineString/MultiLineString features
promoteId: null, // name of a feature property to promote to feature.id. Cannot be used with `generateId`
generateId: false, // whether to generate feature ids. Cannot be used with `promoteId`
updateable: false, // whether the tile index can be updated (with the caveat of a stored simplified copy)
indexMaxZoom: 5, // max zoom in the initial tile index
indexMaxPoints: 100000 // max number of points per tile in the index
});
Expand All @@ -70,6 +71,36 @@ The `promoteId` and `generateId` options ignore existing `id` values on the feat

GeoJSON-VT only operates on zoom levels up to 24.

### Update

For incremental updates to the tile index, you can use `updateData` to change features without having to recreate a fresh index. `updateData` takes a diff object as a parameter with the following properties:

```js
var diff = {
removeAll: false, // set to true to clear all features
remove: ['id1', 'id2'], // array of feature ids to remove
add: [feature1, feature2], // array of GeoJSON features to add
update: [ // array of feature update objects
{
id: 'feature1', // required - id of feature to update
newGeometry: {type: 'Point', ...}, // new geometry for the feature
removeAllProperties: false, // remove all properties
removeProperties: ['prop1', 'prop2'], // array of property keys to remove
addOrUpdateProperties: [ // array of properties to add/update
{key: 'name', value: 'New Name'},
{key: 'population', value: 5000}
]
}
]
};

tileIndex.updateData(diff);
```

All properties in the diff are optional, but at least one operation should be specified. Remove operations are applied before add/update operations.

To use `updateData`, the index must be created with the `updateable: true` option.

### Install

Install using NPM (`npm install geojson-vt`), then:
Expand Down
140 changes: 140 additions & 0 deletions bench/benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import Benchmark from 'benchmark';
import geojsonvt from '../src/index.js';

function generateRectangle(id, centerX, centerY, width, height) {
const corners = [
[centerX - width / 2, centerY + height / 2],
[centerX + width / 2, centerY + height / 2],
[centerX + width / 2, centerY - height / 2],
[centerX - width / 2, centerY - height / 2],
[centerX - width / 2, centerY + height / 2]
];

return {
id,
type: 'Feature',
properties: {name: `Rectangle ${id}`},
geometry: {
type: 'Polygon',
coordinates: [corners]
}
};
}

function generateFeatures(count) {
const features = [];
for (let i = 0; i < count; i++) {
const id = `rect-${i}`;
const centerX = Math.random() * 360 - 180;
const centerY = Math.random() * 180 - 90;
const width = Math.random() * 0.5 + 0.1;
const height = Math.random() * 0.5 + 0.1;
features.push(generateRectangle(id, centerX, centerY, width, height));
}
return features;
}

const optionsConstructor = {
maxZoom: 20,
updateable: false
};

const optionsUpdate = {
maxZoom: 20,
updateable: true
};

const testConfigs = [
{initial: 100, changing: 1, z: 20},
{initial: 100, changing: 10, z: 20},
{initial: 100, changing: 100, z: 20},

{initial: 10000, changing: 1, z: 20},
{initial: 10000, changing: 100, z: 20},
{initial: 10000, changing: 1000, z: 20},

{initial: 100000, changing: 1, z: 20},
{initial: 100000, changing: 100, z: 20},
{initial: 100000, changing: 1000, z: 20}
];

console.log('Starting geojson-vt benchmark - constructor vs updateData:\n');

testConfigs.forEach((config) => {
const suite = new Benchmark.Suite();

const initialFeatures = generateFeatures(config.initial);
const changedFeatures = generateFeatures(config.changing);

// Replace the first N features with changed ones
const updatedFeatures = [...initialFeatures];
for (let i = 0; i < config.changing; i++) {
updatedFeatures[i] = changedFeatures[i];
}

const initialData = {
type: 'FeatureCollection',
features: initialFeatures
};

const updatedData = {
type: 'FeatureCollection',
features: updatedFeatures
};

const removeIds = [];
for (let i = 0; i < config.changing; i++) {
removeIds.push(`rect-${i}`);
}

const tileX = Math.floor(Math.pow(2, config.z) / 2);
const tileY = Math.floor(Math.pow(2, config.z) / 2);

let reusableIndex = geojsonvt(initialData, optionsUpdate);

console.log(`\n${config.initial.toLocaleString()} Initial, ${config.changing.toLocaleString()} changing, getTile z=${config.z}:`);

const results = {};

suite
.add('constructor', () => {
const index = geojsonvt(updatedData, optionsConstructor);
index.getTile(config.z, tileX, tileY);
})
.add('updateData', () => {
reusableIndex.updateData({
remove: removeIds,
add: changedFeatures
});
reusableIndex.getTile(config.z, tileX, tileY);
}, {
onCycle: () => {
reusableIndex = geojsonvt(initialData, optionsUpdate);
}
})
.on('cycle', (event) => {
const benchmark = event.target;
results[benchmark.name] = {
hz: benchmark.hz,
stats: benchmark.stats
};
// console.log(` ${String(benchmark)}`);
})
.on('complete', (event) => {
const benches = event.currentTarget;
const fastest = benches.filter('fastest').map('name')[0];
const slowest = benches.filter('slowest').map('name')[0];

const fastestHz = results[fastest].hz;
const slowestHz = results[slowest].hz;

const percentFaster = (((fastestHz - slowestHz) / slowestHz) * 100).toFixed(0);

console.log(` - ${fastest}: ${percentFaster}% faster`);
})
.run({
async: false
});
});

console.log('Benchmark complete!');
121 changes: 121 additions & 0 deletions debug/update.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
body {
padding: 0;
margin: 0;
font-family: Arial, sans-serif;
}
button {
font-family: Arial, sans-serif;
font-size: 16px;
background: white;
border: 1px solid #ccc;
padding: 5px 10px;
cursor: pointer;
}
button:hover {
border-color: black;
}
.canvas-container {
position: relative;
display: inline-block;
margin-top: 80px;
margin-left: 35px;
}
#canvas {
border: 1px dotted #aaa;
}
#canvas.hover {
background: #cfc;
}
.controls {
position: fixed;
top: 5px;
left: 5px;
display: flex;
align-items: center;
gap: 5px;
z-index: 100;
background: rgba(255, 255, 255, 0.95);
padding: 5px;
border-radius: 3px;
}
#time, #coord {
font-size: 16px;
background: rgba(255, 255, 255, 0.9);
padding: 5px 10px;
border: 1px solid #ccc;
user-select: none;
}
#stats {
position: fixed;
right: 10px;
top: 10px;
background: rgba(255, 255, 255, 0.95);
border: 1px solid #ccc;
padding: 15px;
font-family: monospace;
font-size: 12px;
min-width: 150px;
box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
}
#stats h3 {
margin: 0 0 10px 0;
font-size: 14px;
border-bottom: 1px solid #ddd;
padding-bottom: 5px;
}
#stats .stat-row {
display: flex;
justify-content: space-between;
padding: 3px 0;
}
#stats .stat-label {
color: #666;
}
#stats .stat-value {
font-weight: bold;
color: #333;
}
.nav-button {
position: absolute;
background: rgba(255, 255, 255, 0.7);
border: none;
cursor: pointer;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
z-index: 10;
}
.nav-button:hover {
background: rgba(255, 255, 255, 0.95);
border-color: black;
}
.nav-button:disabled {
opacity: 0.3;
cursor: not-allowed;
}
#navLeft, #navRight {
width: 30px;
writing-mode: vertical-rl;
text-orientation: upright;
top: 30px;
bottom: 30px;
}
#navLeft {
left: -35px;
}
#navRight {
right: -35px;
}
#navUp, #navDown {
height: 30px;
left: 0;
right: 0;
}
#navUp {
top: -35px;
}
#navDown {
bottom: -35px;
}
41 changes: 41 additions & 0 deletions debug/update.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<title>Update</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="update.css">
</head>
<body>
<div class="controls">
<button id="back">z0</button>
<div id="coord">tile: z0-0-0</div>
<button id="add">+1</button>
<button id="remove">-1</button>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="props">Props</button>
<button id="generate">Generate +50k</button>
<button id="removeAll">Remove All</button>
<button id="mode">Mode: Update</button>
<div id="time">Time: <span id="ms">0</span></div>
</div>

<div class="canvas-container">
<button id="navLeft" class="nav-button">◀</button>
<button id="navRight" class="nav-button">▶</button>
<button id="navUp" class="nav-button">▲</button>
<button id="navDown" class="nav-button">▼</button>
<canvas id="canvas"></canvas>
</div>

<div id="stats">
<h3>Tile Index Stats</h3>
<div id="stats_content">No data</div>
</div>

<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src='../geojson-vt-dev.js'></script>
<script src="viz-update.js"></script>
</body>
</html>
Loading