Skip to content

Instantly share code, notes, and snippets.

@jsanz
Last active August 15, 2024 11:41
Show Gist options
  • Save jsanz/ced10d5a11c5c4e31e85 to your computer and use it in GitHub Desktop.
Save jsanz/ced10d5a11c5c4e31e85 to your computer and use it in GitHub Desktop.
Fill the space between two polygons given two points

This is a demo for an SQL function defined in CartoDB. The function takes two adjacent polygons and two points near their borders and return a polygon that fills the space between those polygons up to those points.

To execute the geoprocess draw to adjacent polygons and two points on their borders. Draw a new point or a new polygon to execute again the process.

The SQL called then is:

SELECT ST_AsGeoJSON(
  fillempty(
    polygon1, polygon2,
    point1,point2
    tolerance
  )
) as result

Example

CREATE OR REPLACE FUNCTION fillempty(geometry, geometry, geometry, geometry, double precision)
RETURNS geometry
LANGUAGE plpgsql
AS $function$
DECLARE
-- parameters
g1 ALIAS FOR $1;
g2 ALIAS FOR $2;
p1 ALIAS FOR $3;
p2 ALIAS FOR $4;
tolerance ALIAS FOR $5;
-- work parameters
unionPol geometry;
pp1 geometry; pp2 geometry;
ls geometry;
lsCut geometry;
fromLs float;
toLs float;
convex geometry;
BEGIN
-- Union the polygons
unionPol = ST_Union(
ST_SnapToGrid(g1,tolerance),
ST_SnapToGrid(g2,tolerance)
);
-- If the union is a multi, then buffer and union again
IF ST_GeometryType(unionPol) like 'ST_MultiPolygon' THEN
unionPol = ST_Union(
ST_Buffer(g1,tolerance),
ST_Buffer(g2,tolerance)
);
END IF;
IF ST_GeometryType(unionPol) not like 'ST_Polygon' THEN
RAISE EXCEPTION 'Geometry error';
END IF;
-- Densify the exteriorring to locate the nearest points into the line
ls := ST_Segmentize(ST_ExteriorRing(unionPol),tolerance/2);
pp1 := ST_ClosestPoint(ls,p1);
pp2 := ST_ClosestPoint(ls,p2);
-- Project points into line and extract this new line
fromLs := ST_LineLocatePoint(ls,pp1);
toLs := ST_LineLocatePoint(ls,pp2);
--- Extract the line from one point to the other
IF toLs > fromLS THEN
lsCut := ST_LineSubstring(ls,fromLs,toLs);
ELSE
lsCut := ST_LineSubstring(ls,toLs,fromLs);
END IF;
/* If the length of this cut line is more than 50%
of the original then the reverse line is needed!*/
IF ST_Length(lsCut) > 0.5*ST_Length(ls) THEN
lsCut = ST_Difference(ls,lsCut);
END IF;
-- Simplify again the line
lsCut := ST_Simplify(lsCut,tolerance);
-- difference the convex hull of the line against the poligon
return ST_Difference(ST_ConvexHull(lsCut),unionPol);
END;
$function$
<!DOCTYPE html>
<html>
<head>
<title>Fill space between two points and polygons | CartoDB.js</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" href="http://cartodb.com/assets/favicon.ico" />
<style>
html, body, #map {
height: 100%;
padding: 0;
margin: 0;
}
</style>
<link rel="stylesheet" href="http://libs.cartocdn.com/cartodb.js/v3/3.14/themes/css/cartodb.css" />
<link rel="stylesheet" href="http://leaflet.github.io/Leaflet.draw/leaflet.draw.css">
</head>
<body>
<div id="map"></div>
<!-- include cartodb.js library and the leaflet draw plugin-->
<script src="http://libs.cartocdn.com/cartodb.js/v3/3.14/cartodb.js"></script>
<script src="http://leaflet.github.io/Leaflet.draw/leaflet.draw.js"></script>
<script>
function main() {
var sql = new cartodb.SQL({user: 'jsanz'});
// Create a new map
var map = L.map('map', {
zoomControl: true,
center: [40.43452737429919,-3.699088096618652],
zoom: 16
});
// Set up the base layer
basemap = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
});
basemap.addTo(map);
// Layer for the parameters
var editableLayers = new L.FeatureGroup();
map.addLayer(editableLayers);
// Arrays for the features
var polygons = [], markers = [];
// Layer for the results
var resultLayer = L.geoJson().addTo(map);
// Set up the draw control
var drawControl = new L.Control.Draw({
draw: {
polyline: false,
polygon: {
allowIntersection: false, // Restricts shapes to simple polygons
shapeOptions: {color: '#bada55'}
},
circle: false,
rectangle: false
},
edit: {
featureGroup: editableLayers
}
});
map.addControl(drawControl);
// When a new feature is added put it also on the arrays
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'polygon'){
polygons.push(layer);
if (polygons.length == 3){
editableLayers.removeLayer(polygons.shift());
}
} else {
markers.push(layer);
if (markers.length == 3){
editableLayers.removeLayer(markers.shift());
}
}
editableLayers.addLayer(layer);
});
// When a new feature is added, try to execute the procedure
editableLayers.on('layeradd',function(e){
var sqlStm = "select ST_AsGeoJSON(fillempty(ST_GeomFromText(\'{{g1}}\'),ST_GeomFromText(\'{{g2}}\'),ST_GeomFromText(\'{{p1}}\'),ST_GeomFromText(\'{{p2}}\'),{{tol}})) as result";
// Only execute the procedure if the arrays are full
if (polygons.length == 2 && markers.length == 2){
// SQL Options object
var options = {
g1:getPolWKT(polygons[0]),
g2:getPolWKT(polygons[1]),
p1:getPointWKT(markers[0]),
p2:getPointWKT(markers[1]),
tol: 0.00001 // this parameter really depends on the scale of your features
}
// Execute the SQL
sql.execute(sqlStm,options)
.done(function(data){
// Remove the previous result
resultLayer.removeLayer(resultLayer.getLayers()[0]);
// Add the new geoJSON object
var geom = data.rows[0].result;
resultLayer.addData(eval('('+geom+')'));
})
.error(function(errors){
console.log(errors);
});
}
});
}
// Utility function to generate a Poligon WKT from a GeoJSON
function getPolWKT(polygon){
return 'POLYGON ((' +
polygon.toGeoJSON().geometry.coordinates[0].map(function(p){
return p[0] + ' ' + p[1]})
.join(',') + '))';
}
// Utility function to generate a Point WKT from a GeoJSON
function getPointWKT(point){
return 'POINT (' + point.toGeoJSON().geometry.coordinates.join(' ') + ')';
}
window.onload = main;
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment