Git Product home page Git Product logo

circle-to-polygon's Introduction

Circle To Polygon

The GeoJSON spec does not support circles. If you wish to create an area that represents a circle, your best bet is to create a polygon that roughly approximates the circle. In the limit of the number of edges becoming infinite, your Polygon will match a circle.

circleToPolygon([173.283966, -41.270634], 20000, { numberOfEdges: 32 }) would yield the polygon below:

Example of a polygon with 20000 meter radius, 32 edges and center in 173.283966,-41.270634 (lon,lat)

There is also a port to Go/Golang that can be found here: https://github.com/chrusty/go-circle-to-polygon

Installation

npm install --save circle-to-polygon

or

yarn add circle-to-polygon

Usage

Example

const circleToPolygon = require("circle-to-polygon");

const coordinates = [173.283966, -41.270634]; //[lon, lat]
const radius = 200000; // in meters
const options = { numberOfEdges: 32 }; //optional, defaults to { numberOfEdges: 32 }

const polygon = circleToPolygon(coordinates, radius, options);

console.log(polygon);
/*
{
  type: "Polygon",
  coordinates: [
    [
      [173.283966, -39.47400343176097],
      [172.8297426608343, -39.50761945331798],
      [172.39166717580562, -39.607271255365916],
      [171.98544458449058, -39.76940340765346],
      [171.62589074038397, -39.98820144316868],
      [171.3264802848837, -40.255758887782214],
      [171.09888995216616, -40.56231121046952],
      [170.9525431282912, -40.89653624994988],
      [170.894168491739, -41.24591956982946],
      [170.92739416288478, -41.597181119390946],
      [171.0524081585746, -41.9367562214545],
      [171.26572430426506, -42.251319123422796],
      [171.56009750883513, -42.528331314494025],
      [171.9246304894919, -42.75659019219929],
      [172.3451031959859, -42.92674764018193],
      [172.80453558092947, -43.03176422124745],
      [173.283966, -43.06726456823905],
      [173.76339641907052, -43.03176422124745],
      [174.22282880401409, -42.92674764018193],
      [174.64330151050808, -42.75659019219929],
      [175.00783449116483, -42.528331314494025],
      [175.3022076957349, -42.251319123422796],
      [175.51552384142542, -41.9367562214545],
      [175.6405378371152, -41.597181119390946],
      [175.673763508261, -41.24591956982946],
      [175.61538887170875, -40.89653624994988],
      [175.46904204783382, -40.56231121046952],
      [175.24145171511628, -40.255758887782214],
      [174.94204125961602, -39.98820144316868],
      [174.58248741550943, -39.76940340765346],
      [174.17626482419436, -39.607271255365916],
      [173.73818933916564, -39.50761945331798],
      [173.283966, -39.47400343176097],
    ],
  ],
};
*/

Parameters

  • coordinates Array of length 2 or 3 *required
    • First Element: longitude Number *required, can be any number <=180 and >=-180
    • Second Element: latitude Number *required, can be any number <=90 and >=-90
    • Third Element: Ignored if present
  • radius Number *required, can be any number >0
  • options Object or Number. Omitting this variable is same as passing { numberOfEdges: 32 } and passing a number is same as passing { numberOfEdges: <number> }
    • numberOfEdges Number can be any number >=3. Defaults to 32 when undefined
    • earthRadius Number can be any number >0. Defaults to 6378137 (equatorial Earth radius) when undefined
    • bearing Number can be any number. Defaults to 0 when undefined. How many degrees the circle should be rotated. (Most noticeable for "circles" with few edges.)
    • rightHandRule Boolean default to false when undefined. If true, the circle will be drawn in the opposite direction. This is useful when drawing a hole in another shape, or if your system is following the old standard.

Disclaimers

  • Decimal values will not throw error for numberOfEdges! Instead one of the edges of the polygon will be smaller than the others. In other words, all edges will not have the same length if a decimal number is passed as numberOfEdges.
  • A circle whose edge cross longitude edges (-180 or 180) or a latitude edge (-90 or 90) will contain coordinate points that are outside the standardized coordinates (eg: [182, 23]). This is because there are two ways to represent a line going from [179, x] to [181, y]. One way is simply writing it as [[179, x], [182, y]] while the other is to write it as a multi-polygon. There is a plan to support multi-polygons but it has not yet been implemented.

Authors

  • Gabriel Zimmermann
  • Johannes Jarbratt

Contributors

  • Jan Žák

License

ISC

circle-to-polygon's People

Contributors

dependabot[bot] avatar gabzim avatar janloebel avatar johachi avatar joshm1994 avatar kallewesterling avatar zakjan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

circle-to-polygon's Issues

GoLang port of your library

Today I found myself looking for a GoLang library that can do what your JS one does - converting circles into polygons. I couldn't find anything, so I wrote this: https://github.com/chrusty/go-circle-to-polygon. I've completely copied your math, and am very grateful for your work!

This is not an "issue" as such - just wanted to let you know, and to say thanks.

Add License

We should pick a license and add it to the project files

Create posibility to add more options to be passed to circleToPolygon

Problem Description

Currently the only possible optional configuration of circleToPolygon is numberOfSegments. We want to expand the functionality of circleToPolygon to allow for more optional configurations like creating MultiPolygon (if needed) and to not follow the right-hand rule. We want to do this without increasing the number of parameters for circleToPlygon

Proposed solution

  • Rename the parameter numberOfSegements to options
  • Expand the function to allow the parameter to either be a Number or an Object
    • There should be no behavior changes if options is a Number
    • If options is an Object with a key named numberOfSegemnts, then the value of that key should be used as the value for numberOfSegments
    • If options is an Object but does not have the key numberOfSegements, then then numberOfSegments should default to 32.

The reason that there should not be any error thrown if options is an object without the key numberOfSegements is that all options should be optional. The user might set different options depending on different conditions. We do not want the code to throw an error if one or both of x and y are false.

const options = {}

if (x) {
  options.numberOfSegments = 12;
}

if (y) {
  options.futureOption = futureOptionValue;
}

const polygonCircle = circleToPolygon(center, radius, options)

circleToPolygon's Current Parameters (for reference)

  • center Array *required has to contain two OR three elements,
    • First element is the longitude Number. Has to be >=-180 and <=180
    • Second element is the latitude Number. Has to be >=-90 and <=90
    • Third element is ignored but allowed to follow RFC 7946
  • radius Number *required has to be >0.
  • numberofSegments Number has to be >=3 if passed to the function. Will default to 32.

Coordinates with 3 elements cause error.

In version 2.0.0, the validateCenter function fails when passing a 3rd element (altitude/elevation) as the center point:

if (!Array.isArray(center) || center.length !== 2) {
  throw new Error("ERROR! Center has to be an array of length two");
}

From RFC 7946

A position is an array of numbers. There MUST be two or more
elements. The first two elements are longitude and latitude, or
easting and northing, precisely in that order and using decimal
numbers. Altitude or elevation MAY be included as an optional third
element.

From @types/geojson

/**
 * A Position is an array of coordinates.
 * https://tools.ietf.org/html/rfc7946#section-3.1.1
 * Array should contain between two and three elements.
 * The previous GeoJSON specification allowed more elements (e.g., which could be used to represent M values),
 * but the current specification only allows X, Y, and (optionally) Z to be defined.
 */
export type Position = number[]; // [number, number] | [number, number, number];

The center validation check should be something similar to this:

if (!Array.isArray(center) || ![2,3].includes(center.length)) {
  throw new Error("ERROR! Center has to be an array of length two or three");
}

Option to let user choose to follow right-hand rule or not

Problem

Right now, the polygon returned always has its coordinates ordered counter-clockwise. It is currently not possible to get the coordinates ordered clockwise. Examples when the user might want to have the coordinates clockwise:

  • IF the user wants to create a hole in a shape, as specified here
  • When other parts of the system require them to be clockwise (the old informal specifications also allowed for clockwise polygons)

Proposed Solution

Add option to allow coordinates to be arranged clockwise.

This should be done after #13 has been solved.

Proposed argument:

option = { useLeftHandRule: false }

option = { useLeftHandRule: true }

Default value should default to false when missing.

This package is giving invalid geocodes as polygon coordintaes, when calling with some edge geocode

If we call this package with the below values, it's is giving some point which cannot be plotted in the google map.

var circleToPolygon = require("circle-to-polygon")
const coordinates = [ -179.9745, -22.3023]; //[lon, lat]
const radius = 50000;                           // in meters
const numberOfEdges = 500;                     //optional that defaults to 32

let polygon = circleToPolygon(coordinates, radius, numberOfEdges);

In the result, there are some point like:

[-180.02911542185794, -21.856002776317947] (long lat format) which cannot be plotted in the google map.

Incorrect area of polygons

I am getting incorrect areas of the polygons, thus probably wrong coordinates as well. I can demonstrate the result and reproduce the error by using the Turfjs library. Beside this, I have got same result while working with Google Earth engine. (...the number of pixels showed similar deviation and magnitude of error in an image classification within this computed polygon)

const circleToPolygon = require('circle-to-polygon');
const turf = require('@turf/turf');
 
const coordinates = [10.278653,59.8947275]; //[lon, lat]
const radius = 1610;                           // in meters
const numberOfEdges =32;                     //optional that defaults to 32
 
let polygon = circleToPolygon(coordinates, radius, numberOfEdges);

let areaOfPolygon = turf.area(polygon)
let areaMath = radius * radius * Math.PI / 2

console.log(JSON.stringify(polygon))
console.log('Area by Turf: ', areaOfPolygon)
console.log('Area by math: ', areaMath)

Result:

~/projects/circle_to_polygon (master) $ node to_polygon.js
{"type":"Polygon","coordinates":[[[10.278653,59.90919037607431],[10.284280645205657,59.908912356083924],[10.28969174946274,59.90808899392172],[10.294678135098811,59.90675196982125],[10.299048023224346,59.904952723057185],[10.302633427274063,59.90276046629221],[10.30529661686024,59.90025951531567],[10.306935402293933,59.897546038387084],[10.30748703798763,59.89472435169742],[10.306930598361534,59.89190290465795],[10.305287740350176,59.889190110195436],[10.302621829553926,59.8866901806016],[10.299035469941463,59.884499128639774],[10.294666537376706,59.88270108667902],[10.2896828729499,59.881365083961555],[10.284275841271294,59.88054240426573],[10.278653,59.88026462392567],[10.273030158728705,59.88054240426573],[10.2676231270501,59.881365083961555],[10.262639462623294,59.88270108667902],[10.258270530058537,59.884499128639774],[10.254684170446074,59.8866901806016],[10.252018259649825,59.889190110195436],[10.250375401638466,59.89190290465795],[10.249818962012368,59.89472435169742],[10.250370597706066,59.897546038387084],[10.25200938313976,59.90025951531567],[10.254672572725935,59.90276046629221],[10.258257976775651,59.904952723057185],[10.26262786490119,59.90675196982125],[10.267614250537259,59.90808899392172],[10.273025354794344,59.908912356083924],[10.278653,59.90919037607431]]]}
Area by Turf:  8091097.936206874
Area by math:  4071661.1586850514

I understand that the polygon shape has a different area than a perfect circle, but the error shows approx. double area than a mathematically computed one.

Drawing a semi circle

Hi!
I need to be able to convert semi circles and quarter circles to geojson. I wonder if there’s any way to modify this script easily to accommodate for that.
Thanks

New Release / Version NPM

Hi,

I would need a new release of the current version of circle-to-polygon. You've merged my pull request but I would need it available via npm.

Could you please make a new release please?! :)

Best regards,
Jan

Oval Polygon

Hello, I put the polygon returned by the function on a map but at the end I got an oval shape
Where can I be wrong?

See:

Rotate output

Is there a way to rotate the output? Currently the output results in a diamond. Ideally I'd like to rotate it 45 degrees so that it represents a square as opposed to a diamond. The bearing option doesn't seem to do anything when I try to set that to 45.

Allow center argument to be of type `object` or `array`

Problem Description

There has been several issues where the user has passed the longitude and latitude in opposite order in the array passed as the center argument. This has been a cause that users has gotten "incorrect" circles.

The problem might already have been mitigated some since circleToPolygon since version 2.0.0 reject latitude values larger than ±90, but can still occur when a longitude smaller than ±90 is passed as the latitude value.

Proposed solution

The problem could be further mitigated by letting the user pass the coordinates as an object (in addition to passing it as an array).

Expand the function to allow the parameter center to be an Object of any of the following compositions.

center = { latitude: latValue, longitude: lngValue }

OR

center = { lat: latValue, lon: lngValue }

OR

center = { lat: latValue, lng: lngValue }

where latValue and lngValue are valid latitude and longitude values as stated earlier.

circleToPolygon's Current Parameters (for reference)

  • center Array *required has to contain two OR three elements,
    • First element is the longitude Number. Has to be >=-180 and <=180
    • Second element is the latitude Number. Has to be >=-90 and <=90
    • Third element is ignored but allowed to follow RFC 7946
  • radius Number *required has to be >0.
  • numberofSegments Number has to be >=3 if passed to the function. Will default to 32.

Circle to polygon returns oval/ellipse instead of a shape like circle

I used following code to return a polygon which is approximately like a circle.

let coordinates = [28.612484207825005, 77.23574939044147]; 

let radius = 311;      

let numberOfEdges = 32;       
 
let polygon = circleToPolygon(coordinates, radius, numberOfEdges);

var coords = polygon.coordinates;

When I used coords and created a polygon shape using Google Maps JS API, it showed a shape like oval (2 ends were very far from each other, two very near, hope you understand).

In fact, the different in distances is approx. 4:1 ratio. So it's never a shape closer to circle.

Is there any issue, or is it supposed to do this intentionally?

Discussion: Changing the Signatur of the Function

I'm currently working on fixing the problem regarding out of range values (see #4 and #6 ). Out of range problem occur when the edge of the circle cross the longitude value 180 or when the circle include any of the polar circles.

When doing this I started to think about the functions signature, or more specific, it's arguments

circleToPolygon(center, radius, numberOfSegments)

Solving the problem with too large positive or negative lat and lng values has to be solved by creating a MultiPolygon instead of the regular Polygon. I realize that this would also change the output object as well.

However, some implementations prefer to large lat/lng values over MultiPolygon. Because of that, I wanted to make using MultiPolygon to be an option. At the same time, we don't want to keep on increasing arguments. One solution could be to replace either numberOfSegments or both radius and numberOfSegemnts with an object called config.

The new arguments would then be just center and config and the function call would be circleToPolygon(center, config) where config could look something like this:

// example is with default values
const config = {
  radius: 1500,
  numberOfSegments: 32,
  useRightHandRule: true,
  MultiPolygon: true
}

I could make the function backwards compatible by checking variable type and for a third argument, but maybe a version bump and having breaking changes in the readme would give more clean code. What do you think? @gabzim

Out of range values for longitude and latitude

Hi, I noticed that if the center of the circle is located up north (e.g. [24, 60]) and the radius is a big number like 5000000( 5000 KM) the output includes out of range values for longitude and latitude.
I resolved it by adding the following part before returning the result but wondering if there are cleaner ways of doing it?

for (let i = 0; i < coordinates.length; i++) {
        if (coordinates[i][0] > 180)   coordinates[i][0] = 180;
        if (coordinates[i][0] < -180)  coordinates[i][0] = -180;
        if (coordinates[i][1] < -90) coordinates[i][1] = -90;
        if (coordinates[i][1] > 90)  coordinates[i][1] = 90;
        // console.log(coordinates[i][0], coordinates[i][1]);
    }

Extend `numberOfEdges` to accept a callback

It would be very useful if the numberOfEdges option could accept a callback which would calculate the number of edges based on the size/radius/circumference of the circle.

The new usage might look something like this:

const circleToPolygon = require("circle-to-polygon");

const coordinates = [-27.4575887, -58.99029];
const radius = 100;
const numberOfEdges = ({ c }) => c / 4;

let polygon = circleToPolygon(coordinates, radius, { numberOfEdges });

And the definition for getNumberOfEdges could be adjusted to this:

const DEFAULT_NUMBER_OF_EDGES = 32;

function getNumberOfEdges(radius, options) {
  if (!options) return DEFAULT_NUMBER_OF_EDGES;
  if (!isObjectNotArray(options)) return options;

  if (typeof options.numberOfEdges === "function") {
    const circumference = 2 * Math.PI * radius;
    const numberOfEdges = options.numberOfEdges({ c: circumference, r: radius });
    if (Number.isFinite(numberOfEdges)) return Math.round(numberOfEdges);
    return DEFAULT_NUMBER_OF_EDGES;
  }

  return options.numberOfEdges ?? DEFAULT_NUMBER_OF_EDGES;
}

Add typings

@johachi How do you feel about us either:
a. Porting this to typescript
b. Adding a typings file for people consuming this from typescript?

Right now we're limited to using var etc on our source. If we don't want to add typing to the module maybe we could at least at a transpilation step via babel?

What are your thoughts on this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.