ol-ext/examples/layer/map.source.delaunay.html
2021-03-20 08:30:19 +01:00

335 lines
9.1 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>ol-ext: Delaunay triangulation</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="Calculate the Delaunay triangulation of a Point vector source." />
<meta name="keywords" content="ol, openlayers, source, vector, tirangulation, delaunay, voronoi, TIN" />
<!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<!-- Openlayers -->
<link rel="stylesheet" href="https://openlayers.org/en/latest/css/ol.css" />
<script type="text/javascript" src="https://openlayers.org/en/latest/build/ol.js"></script>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<!-- ol-ext -->
<link rel="stylesheet" href="../../dist/ol-ext.css" />
<script type="text/javascript" src="../../dist/ol-ext.js"></script>
<!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
<script src="https://unpkg.com/elm-pep"></script>
<!-- filesaver-js -->
<script type="text/javascript" src="https://cdn.rawgit.com/eligrey/FileSaver.js/aa9f4e0e/FileSaver.min.js"></script>
<link rel="stylesheet" href="../style.css" />
<style>
.ol-control.ol-bar .ol-control.ol-text-button:hover {
background: #33669977;
}
.ol-control.ol-bar .ol-control.ol-text-button.ol-active {
background: #369;
}
.ol-control.ol-bar .ol-control.ol-text-button.ol-active > div,
.ol-control.ol-bar .ol-control.ol-text-button > div:hover {
color: #fff;
}
</style>
</head>
<body >
<a href="https://github.com/Viglino/ol-ext" class="icss-github-corner"><i></i></a>
<a href="../../index.html">
<h1>ol-ext: Delaunay triangulation</h1>
</a>
<div class="info">
<p>
The <i>ol/source/Delaunay</i> computes a
<a href="https://en.wikipedia.org/wiki/Delaunay_triangulation">
Delaunay triangulation
</a>
of a vector source (points).
</p>
<p>
<span class="experimental">Experimental</span>
this feature is still in progress and may be buggy.
</p>
<p>
NB: the algorithm is incremental and not very efficient but it computes the triangulation directly in a <i>ol/source/Vector</i>.
<br/>
You can add/remove vertex from the point source and get the resulting triangles directly in the triangle source.
<br/>
If you're looking for more efficient algorithm you'd better look at something like
<a href="https://github.com/mapbox/delaunator#performance">
mapbox/delaunator
</a>
</p>
<p>
This is more to use with interactive processes to maintains a triangulation on state while interacting.
</p>
</div>
<!-- Map div -->
<div id="map" style="width:600px; height:400px;"></div>
<div class="options">
<ul>
<li>
<input type="number" value="20" />
<button onclick="generate(Number($(this).prev().val()))">Generate!</button>
</li>
<li>
<label><input type="checkbox" id="voronoi"/> show Voronoi</label>
(<label><input type="checkbox" id="border"/> border</label>)
</li>
<li>
<button onclick="save('delaunay')">Save Delaunay</button>
<button onclick="save('voronoi')">Save Voronoi</button>
</li>
<li>
Generated triangles
<ul class='triangles'></ul>
</li>
</ul>
</div>
<script type="text/javascript">
function generate(nb) {
var t = new Date();
var ext = map.getView().calculateExtent(map.getSize());
for (var i=0; i<nb; ++i) {
var f = new ol.Feature(new ol.geom.Point([ext[0]+(ext[2]-ext[0])*Math.random(), ext[1]+(ext[3]-ext[1])*Math.random()]));
nodes.addFeature(f);
}
console.log('Elapsed',((new Date())-t)/1000+'s');
}
// Base layers
var stamen = new ol.layer.Tile({
title: "Watercolor",
baseLayer: true,
source: new ol.source.Stamen({
layer: 'watercolor'
})
});
// The map
var map = new ol.Map ({
target: 'map',
view: new ol.View ({
zoom: 4,
center: [205461, 5867916]
}),
layers: [stamen]
});
// Default style
var fill = new ol.style.Fill({
color: 'rgba(255,255,255,0.4)'
});
var stroke = new ol.style.Stroke({
color: '#3399CC',
width: 1.25
});
var style = new ol.style.Style({
image: new ol.style.RegularShape({
stroke: stroke,
points: 4,
radius: 5,
radius2: 0
}),
text: new ol.style.Text({
font: 'bold 10px sans-serif',
text:'test',
textAlign: 'left',
textBaseline: 'bottom',
offsetX: 3,
offsetY: -3
}),
fill: fill,
stroke: stroke
});
var redStyle = new ol.style.Style({
fill: new ol.style.Fill({ color: [255,0,0,.2] })
});
var voroStyle = new ol.style.Style({
stroke: new ol.style.Stroke({ color: [128,0,0,.5], width: 1.25 }),
fill: new ol.style.Fill({ color: [128,0,0,.1] })
});
// Node source
var nodes = new ol.source.Vector();
var nb = 0;
nodes.on('addfeature', function(e) {
e.feature.set('id', nb++);
});
var vector = new ol.layer.Vector({
name: 'vector',
source: nodes,
style: function(f) {
style.getText().setText(String(f.get('id')||'0'));
return style;
}
});
// Delaunay source based on the node source
var delaunay = new ol.source.Delaunay({
source: nodes
});
var triangle = new ol.layer.Vector({
name: 'triangle',
source: delaunay
});
// In the map
map.addLayer(triangle);
map.addLayer(vector);
// Control Bar
var bar = new ol.control.Bar({ toggleOne: true });
map.addControl(bar);
// Draw interaction to add new points
var draw = new ol.interaction.Draw({ source: nodes, type:"Point" });
bar.addControl(new ol.control.Toggle({
className: 'ol-text-button',
html: "add",
interaction: draw,
active: true
}));
// Delete
var del = new ol.interaction.Select({
layers: [vector],
hitTolerance: 5
});
del.on('select', function(e){
var f = e.selected[0];
if (f) nodes.removeFeature(f);
del.getFeatures().clear();
});
bar.addControl(new ol.control.Toggle({
className: 'ol-text-button',
html: "del",
interaction: del
}));
/*
// Draw interaction to add new points
var modify = new ol.interaction.Modify({ source: nodes });
bar.addControl(new ol.control.Toggle({
className: 'ol-text-button',
html: "mod",
interaction: modify,
}));
modify.on('modifystart', function(e){
console.log(e)
var f = modify.dragSegments_[0][0].feature;
//nodes.removeFeature(f)
});
modify.on('modifyend', function(e){
console.log(e)
var f = modify.dragSegments_[0][0].feature;
//nodes.addFeature(f);
});
*/
// Select
var circle = new ol.layer.Vector({
name: 'circle',
source: new ol.source.Vector(),
style: redStyle
});
map.addLayer(circle);
// Voronoi
var voronoi = new ol.layer.Vector({
name: 'Voronoi',
source: new ol.source.Vector(),
style: voroStyle
});
map.addLayer(voronoi);
var tout
delaunay.on('addfeature', function() {
if (tout) clearTimeout(tout);
tout = setTimeout(calculateVoronoi, 500);
});
function calculateVoronoi () {
voronoi.getSource().clear();
if ($('#voronoi').prop('checked')) {
var features = [];
delaunay.calculateVoronoi($('#border').prop('checked')).forEach(function(f) {
features.push (f);
});
voronoi.getSource().addFeatures(features);
}
}
$('#voronoi').on('change', calculateVoronoi);
$('#border').on('change', calculateVoronoi);
// Show circumcircle on hover
var hover = new ol.interaction.Hover({
layers: [triangle]
});
hover.on('enter', function(e){
circle.getSource().clear();
if (e.layer===triangle) {
var c = e.feature.getGeometry().getCoordinates()[0];
c = delaunay.getCircumCircle(c);
if (c.radius) {
c = new ol.geom.Circle(c.center, c.radius);
var f = new ol.Feature(c);
circle.getSource().addFeature(f);
}
}
});
hover.on('leave', function(e){
circle.getSource().clear();
});
bar.addControl(new ol.control.Toggle({
className: 'ol-text-button',
html: 'circle',
interaction: hover
}));
// Import / export data
var dd = new ol.interaction.DropFile();
map.addInteraction (dd);
dd.on('loadstart', function () { $('body').addClass('waiting'); });
dd.on('loadend', function () { $('body').removeClass('waiting'); });
dd.on ('addfeatures', function(event) {
var features = [];
delaunay.clear();
event.features.forEach(function(f) {
if (f.getGeometry().getType()==='Point') nodes.addFeature(f);
});
});
function save(what) {
var format = new ol.format.GeoJSON();
var features;
if (what === 'delaunay') {
features = delaunay.getFeatures();
} else {
features = voronoi.getSource().getFeatures();
}
var data = format.writeFeatures(features, {
dataProjection: 'EPSG:4326',
featureProjection: map.getView().getProjection()
});
var blob = new Blob([data], {type: "text/plain;charset=utf-8"});
saveAs(blob, what+'.geojson');
}
</script>
</body>
</html>