mirror of
https://github.com/Viglino/ol-ext.git
synced 2026-01-25 17:36:21 +00:00
333 lines
9.1 KiB
HTML
333 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://cdn.jsdelivr.net/npm/ol@latest/ol.css" />
|
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/ol@latest/dist/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.StadiaMaps({ layer: 'stamen_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> |