zizu 112233
zizu 112233

Reputation: 3

geojson lat lon polygon draw smaller inner polygon

I need to write a function that takes a GeoJSON based on lat and lon values and draws polygon to 2d canvas.

In addition to that I need to add some distance and draw inner polygon where 4 sides (north,east,west,south) of the polygon will have different distances from outer polygon. I have a problem with edge coordinates being output in one dimension only, where somehow when drawing a polygon some of the point/lines need to be offset by a single side either north or south etc... or in some cases 2 sides need to be taken into account.

Can anyone point me in the direction of the solution? So far most of the lines are drawn correctly, except for the edge cases in the green circles.

Not correct not correct

The correct image should be like this

Correct

enter image description here

The codepen of current implementation

https://codepen.io/Zlatko-Memisevic/pen/qBGNVgj

const exampleGeoJson = {
  "type": "FeatureCollection",
  "features": [{
    "type": "Feature",
    "geometry": {
      "type": "Polygon",
      "coordinates": [
        [
          [9.409277330768161, 47.330294996637527],
          [9.40929555711242, 47.330394995087424],
          [9.409449188016863, 47.330385694636568],
          [9.409441872135783, 47.330340644668418],
          [9.409432145964761, 47.330341377653177],
          [9.409430621953572, 47.330332010609965],
          [9.409383287787568, 47.330335311475089],
          [9.409376169945089, 47.330288098791449],
          [9.409349057871147, 47.330289992509627],
          [9.409347814140572, 47.330283553765319],
          [9.409279252163072, 47.330288260899749],
          [9.409281541354549, 47.330294699779891],
          [9.409277330768161, 47.330294996637527]
        ]
      ]
    }
  }]
};

function drawGeoJsonOnCanvas(geoJson, canvasId, offsets) {
  const canvas = document.getElementById(canvasId);
  const ctx = canvas.getContext('2d');

  if (!canvas) {
    console.error(`Canvas element with id "${canvasId}" not found.`);
    return;
  }

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  const bbox = turf.bbox(geoJson);
  const scaleX = canvas.width / (bbox[2] - bbox[0]);
  const scaleY = canvas.height / (bbox[3] - bbox[1]);

  function drawPolygon(coordinates, dotted, labelSides = false) {
    ctx.beginPath();
    coordinates.forEach((coord, index) => {
      const x = (coord[0] - bbox[0]) * scaleX;
      const y = (bbox[3] - coord[1]) * scaleY;

      if (index === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
    });
    ctx.closePath();
    if (dotted) {
      ctx.setLineDash([5, 5]);
    } else {
      ctx.setLineDash([]);
    }
    ctx.stroke();

    if (labelSides) {
      for (let i = 0; i < coordinates.length - 1; i++) {
        const start = coordinates[i];
        const end = coordinates[i + 1];
        const mid = turf.midpoint(start, end);

        const midX = (mid.geometry.coordinates[0] - bbox[0]) * scaleX;
        const midY = (bbox[3] - mid.geometry.coordinates[1]) * scaleY;

        const length = turf.distance(start, end, {
          units: 'meters'
        }).toFixed(2);

        ctx.fillText(`${length}m`, midX, midY);
      }
    }
  }

  function getOffsetDistance(direction, offsets) {
    if (direction >= -45 && direction < 45) {
      return offsets.east;
    } else if (direction >= 45 && direction < 135) {
      return offsets.north;
    } else if (direction >= -135 && direction < -45) {
      return offsets.south;
    } else {
      return offsets.west;
    }
  }

  const features = geoJson.features;

  features.forEach(feature => {
    const coordinates = feature.geometry.coordinates[0];
    drawPolygon(coordinates, false, true);

    const offsettedCoordinates = [];
    for (let i = 0; i < coordinates.length - 1; i++) {
      const start = coordinates[i];
      const end = coordinates[(i + 1) % coordinates.length];

      const line = turf.lineString([start, end]);
      const direction = turf.bearing(start, end);

      const offsetDistance = getOffsetDistance(direction, offsets);
      const offsettedLine = turf.transformTranslate(line, offsetDistance, direction + 90, {
        units: 'centimeters'
      });

      offsettedCoordinates.push(offsettedLine.geometry.coordinates[0]);
      offsettedCoordinates.push(offsettedLine.geometry.coordinates[1]);
    }

    const uniqueOffsettedCoordinates = Array.from(new Set(offsettedCoordinates.map(JSON.stringify))).map(JSON.parse);
    uniqueOffsettedCoordinates.push(uniqueOffsettedCoordinates[0]); // Close the polygon

    const innerPolygon = turf.polygon([uniqueOffsettedCoordinates]);

    if (innerPolygon.geometry.type === 'Polygon') {
      const innerCoordinates = innerPolygon.geometry.coordinates[0];
      drawPolygon(innerCoordinates, true);
    }
  });
}

// Example usage
document.addEventListener('DOMContentLoaded', () => {
  const offsets = {
    north: 50, // in centimeters
    south: 50, // in centimeters
    east: 50, // in centimeters
    west: 50 // in centimeters
  };
  drawGeoJsonOnCanvas(exampleGeoJson, 'geoCanvas', offsets);
});
#geoCanvas {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/6.5.0/turf.min.js"></script>
<canvas id="geoCanvas" width="800" height="600"></canvas>

Upvotes: 0

Views: 103

Answers (1)

James Beard
James Beard

Reputation: 340

Have never seen an out of the box solution for what you're trying to do.

One novel solution would be:

  1. taking each corner in turn
  2. calculate the bearing and distance from that corner to where the intersection of the two offset lines would meet
  3. join all those points together into a new polygon

It would be fiddly and involve some math, though should be pretty robust, at least for shapes as complex as the one you've shown.

Upvotes: 0

Related Questions