0

In my previous questions, how to split a polygon into rectangles of a specific length, this is the code i arrived at in the end with the help of a fellow programmer.

Splitting polygon with turf.rectangleGrid in vue.js Leaflet map

In this question, i want to further dive in and ask if it's possible to split a rectangle using this method into 10 rectangles and change the color of the rectangles for example if the area is 30% complete then 3 squares would be highlighted out of the 10. This is to visually aid the end-user. So the function would take in 1-100 or 1-10 as a number and highlight that number of squares.

This is the code from the last Q&A which ends up with the result in the picture.

let coordsLayer = L.geoJSON(coords).addTo(map);

let bbox = turf.bbox(coords);

let cellWidth = 0.05;
let cellHeight = 0.05;

let bufferedBbox = turf.bbox(turf.buffer(coords, cellWidth, {units: 'kilometers'}));
let options = { units: "kilometers", mask: coords};
let squareGrid = turf.squareGrid(
  bufferedBbox,
  cellWidth,
  options
);

let clippedGridLayer = L.geoJSON().addTo(map);
turf.featureEach(squareGrid, function (currentFeature, featureIndex) {
  let intersected = turf.intersect(coords.features[0], currentFeature);
  clippedGridLayer.addData(intersected);
});
map.fitBounds(clippedGridLayer.getBounds());

So basically in the project an area or a block would be displayed as a polygon on the map. Any polygon for that matter. Those areas have tasks assigned to them, the tasks go from 1% completed to 100% completed and we want a visual aid on the polygons to represent its completeness. The input of the function would be a JSON object like in the code above so a polygon, and the other input would be an integer 1-100 taken from the task progress.

The goal here is to have a specif percent of the polygon highlighted, it does not even have to be filled with rectangles like in the previous qs, we just need to visually represent how much of the area is done by filling or highlighting a percentage of any given polygon.

So if the task is 15% done the polygon would be highlighted like this approximately.

3
  • 1
    It's not clear what you mean by "to split a rectangle using this method into 10 rectangles" and "change the color of the rectangles for example if the area is 30% complete then 3 squares would be highlighted out of the 10". Please edit your question and add an image that illustrates what you want to achieve.
    – TomazicM
    Jun 30, 2021 at 18:54
  • Thanks for getting back onto me, I have edited the question now hope it helps to understand what I'm after. @TomazicM i really hope you could have a look as its very important for the project, thanks! Jul 1, 2021 at 8:16
  • please let me know if this is achievable Jul 1, 2021 at 10:57

1 Answer 1

2

If you would have described at the beginning what are you trying to achieve (instead of how you are trying to do it), things would be much simpler.

But first, how to do it with the grid. Order of polygons in the grid is from bottom up, from left to right. Taking that into account when clipping grid elements, area can be cumulatively calculated and grid elements property isDone set to true for elements where cumulative area is under reached percentage, and to false for those that above it. This property can then be used for styling background color of elements.

Code could then look something like this:

let coordsLayer = L.geoJSON(coords).addTo(map);
map.fitBounds(coordsLayer.getBounds());

let bbox = turf.bbox(coords);

let cellWidth = 0.05;
let cellHeight = 0.05;

let bufferedBbox = turf.bbox(turf.buffer(coords, cellWidth, {units: 'kilometers'}));
let options = { units: "kilometers", mask: coords};
let squareGrid = turf.squareGrid(
  bufferedBbox,
  cellWidth,
  options
);

let clippedGridLayer = L.geoJSON(null, {
  style: function(feature) {
    let color, opacity, weight;
    if (feature.properties.isDone) {
      color = 'yellow';
      opacity = 1;
      weight = 1;
      }
    else {
      color = '';
      opacity = 0;
      weight = 0;
    }
    return {
      fillColor: color,
      fillOpacity: opacity,
      color: color,
      weight: weight,
      opacity: opacity
    };
  }
}).addTo(map);

let percDone = 12;

let totalArea = turf.area(coords);
let sumPieceArea = 0;

turf.featureEach(squareGrid, function (currentFeature, featureIndex) {
  let intersected = turf.intersect(coords.features[0], currentFeature);
  let pieceArea = turf.area(intersected);
  sumPieceArea += pieceArea;
  intersected.properties.isDone = ((sumPieceArea / totalArea) * 100) <= percDone;
  clippedGridLayer.addData(intersected);
});

Result would then look like this:

enter image description here

This obviously does not look very nice.

Much better method would be to take bounding box, and while varying it's width by bisection method calculate clipped area with turf.bboxClip method, repeating this until percentage of clipped area matched target percentage.

In this case code could look something like this:

let coordsLayer = L.geoJSON(coords).addTo(map);
map.fitBounds(coordsLayer.getBounds());

let bbox = turf.bbox(coords);

let percDone = 12;

let minX = bbox[0];
let maxX = bbox[2];
let totalArea = turf.area(coords);
let prevMaxX, clippedPolygon, clippedArea, percClippedArea;

do {
  clippedPolygon = turf.bboxClip(coords.features[0], bbox);
  clippedArea = turf.area(clippedPolygon);
  percClippedArea = (clippedArea / totalArea) * 100;
  if (Math.abs(percClippedArea - percDone) < 1) break;
  if (percClippedArea > percDone) {
    prevMaxX = maxX;
    maxX = minX + ((maxX - minX) / 2);
    }
  else {
    maxX = maxX + ((prevMaxX - maxX) / 2);
  }
  bbox[2] = maxX;
  }
while (true);

L.geoJSON(clippedPolygon, {
  onEachFeature: function(feature, layer) {
    layer.bindTooltip(percDone + '%', {permanent: true, direction: 'center', className: 'perc-tooltip'});
  },
  style: function(feature){
    return {fillColor: 'yellow', fillOpacity: 1, weight: 0};
  }
}).addTo(map);

Result is much nicer now:

enter image description here

1
  • Just what we were looking for, thanks so much! Jul 2, 2021 at 12:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.