Skip to content

Instantly share code, notes, and snippets.

@richardaum
Last active July 24, 2020 19:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richardaum/3266f3882a1bd010b777049f6510abec to your computer and use it in GitHub Desktop.
Save richardaum/3266f3882a1bd010b777049f6510abec to your computer and use it in GitHub Desktop.
Create/generate a KML file withing multiple circles varying its radius, central point, and number of points to represent the circle

Script created to generate KML with circles using multiple points generated from a lat long coordinates. Currently, it is being used with 17 points to fulfil a requriment from Consumer app.

Requirements

Ruby (thanks to https://github.com/mbrookes/kml_polygon for the polygon algorithm) Node

Usage

node circle.js > area-de-entrega.kml

For Consumer app, you must format/beautify your XML to prevent errors. Currently, I'm using the following VSCode extension https://marketplace.visualstudio.com/items?itemName=DotJoshJohnson.xml.

Internal variables (circle.js)

  • distances = Array containing multiple radius values related to how many will be created
  • latitude = Lat from the central coordinate
  • longitude = Lon from the central coordinate
  • radius = Circle radius
  • numberOfPoints = Number of points used to create the circle (20 is recommended)
const { execSync } = require("child_process");
const distances = [330, 550, 800, 1000, 1500, 3000, 4000, 7000];
const latitude = -3.0236999;
const longitude = -59.9598609;
const radius = 330;
const numberOfPoints = 17;
const placemarks = distances
.map((distance) => {
const output = execSync(
`ruby kml_polygon_example.rb ${longitude} ${lat} ${radius} ${numberOfPoints}`
).toString();
const placemark = `
<Placemark>
<name>${distance}m</name>
<styleUrl>#poly-000000-1200-77</styleUrl>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<tessellate>1</tessellate>
${output}
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
`;
return placemark;
})
.join("\n");
const kmlTemplate = `
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Área de entrega</name>
<description/>
<Style id="poly-000000-1200-77-normal">
<LineStyle>
<color>ff000000</color>
<width>1.2</width>
</LineStyle>
<PolyStyle>
<color>4d000000</color>
<fill>1</fill>
<outline>1</outline>
</PolyStyle>
</Style>
<Style id="poly-000000-1200-77-highlight">
<LineStyle>
<color>ff000000</color>
<width>1.8</width>
</LineStyle>
<PolyStyle>
<color>4d000000</color>
<fill>1</fill>
<outline>1</outline>
</PolyStyle>
</Style>
<StyleMap id="poly-000000-1200-77">
<Pair>
<key>normal</key>
<styleUrl>#poly-000000-1200-77-normal</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#poly-000000-1200-77-highlight</styleUrl>
</Pair>
</StyleMap>
<Folder>
<name>Circles</name>
${placemarks}
</Folder>
</Document>
</kml>
`;
console.log(kmlTemplate);
#require 'kml_polygon/version'
module KmlPolygon
extend self
include Math
# constant to convert to degrees
DEGREES = 180.0 / PI
# constant to convert to radians
RADIANS = PI / 180.0
# Mean Radius of Earth in meters
EARTH_MEAN_RADIUS = 6371.0 * 1000
#
# Convert [x,y,z] on unit sphere
# back to [longitude, latitude])
#
def to_earth(point)
point[0] == 0.0 ? lon = PI / 2.0 :lon = Math.atan(point[1]/point[0])
lat = PI / 2.0 - Math.acos(point[2])
# select correct branch of arctan
(point[1] <= 0.0 ? lon = -(PI - lon) : lon = PI + lon) if point[0] < 0.0
[lon * DEGREES, lat * DEGREES]
end
#
# Convert [longitude, latitude] IN RADIANS to
# spherical / cartesian [x,y,z]
#
def to_cartesian(coord)
theta = coord[0]
# spherical coordinate use "co-latitude", not "latitude"
# lat = [-90, 90] with 0 at equator
# co-lat = [0, 180] with 0 at north pole
phi = PI / 2.0 - coord[1]
[Math.cos(theta) * Math.sin(phi), Math.sin(theta) * Math.sin(phi), Math.cos(phi)]
end
# spoints -- get raw list of points in longitude,latitude format
#
# radius: radius of polygon in meters
# sides: number of sides
# rotate: rotate polygon by number of degrees
#
# Returns a list of points comprising the object
#
def spoints(lon, lat, radius, sides=20, rotate=0)
rotate_radians = rotate * RADIANS
# compute longitude degrees (in radians) at given latitude
r = radius / (EARTH_MEAN_RADIUS * Math.cos(lat * RADIANS))
vector = to_cartesian([lon * RADIANS, lat * RADIANS])
point = to_cartesian([lon * RADIANS + r, lat * RADIANS])
points = []
for side in 0...sides
points << to_earth(rotate_point(vector, point, rotate_radians + (2.0 * PI/sides)*side))
end
# Connect to starting point exactly
# Not sure if required, but seems to help when the polygon is not filled
points << points[0]
end
#
# rotate point around unit vector by phi radians
# http://blog.modp.com/2007/09/rotating-point-around-vector.html
#
def rotate_point(vector, point, phi)
# remap vector for sanity
u, v, w, x, y, z = vector[0], vector[1], vector[2], point[0], point[1], point[2]
a = u*x + v*y + w*z
d = Math.cos(phi)
e = Math.sin(phi)
[(a*u + (x - a*u)*d + (v*z - w*y) * e),
(a*v + (y - a*v)*d + (w*x - u*z) * e),
(a*w + (z - a*w)*d + (u*y - v*x) * e)]
end
#
# Output points formatted as a KML string
#
# You may want to edit this function to change "extrude" and other XML nodes.
#
def points_to_kml(points)
kml_string = "<coordinates>"
points.each do |point|
kml_string << point[0].to_s << "," << point[1].to_s << ",0" << "\n"
end
kml_string << '</coordinates>'
# kml_string << " <extrude>1</extrude>\sides"
# kml_string << " <altitudeMode>clampToGround</altitudeMode>\sides"
end
#
# kml_regular_polygon - Regular polygon
#
# (lon, lat) - center point in decimal degrees
# radius - radius in meters
# segments - number of sides, > 20 looks like a circle (optional, default: 20)
# rotate - rotate polygon by number of degrees (optional, default: 0)
#
# Returns a string suitable for adding into a KML file.
#
def kml_regular_polygon(lon, lat, radius, segments=20, rotate=0)
points_to_kml(spoints(lon, lat, radius, segments, rotate))
end
#
# kml_star - Make a "star" or "burst" pattern
#
# (lon, lat) - center point in decimal degrees
# radius - radius in meters
# innner_radius - radius in meters, typically < outer_radius
# segments - number of "points" on the star (optional, default: 10)
# rotate - rotate polygon by number of degrees (optional, default: 0)
#
# Returns a string suitable for adding into a KML file.
#
def kml_star(lon, lat, radius, inner_radius, segments=10, rotate=0)
outer_points = spoints(lon, lat, radius, segments, rotate)
inner_points = spoints(lon, lat, inner_radius, segments, rotate + 180.0 / segments)
# interweave the radius and inner_radius points
# I'm sure there is a better way
points = []
for point in 0...outer_points.length
points << outer_points[point]
points << inner_points[point]
end
# MTB - Drop the last overlapping point leaving start and end points connecting
# (resulting output differs from orig, but is more correct)
points.pop
points_to_kml(points)
end
end
#
# Examples
#
# puts KmlPolygon::kml_star(45,45, 70000, 50000)
# puts KmlPolygon::kml_regular_polygon(50, 50, 70000)
puts KmlPolygon::kml_regular_polygon(ARGV[0].to_i, ARGV[1].to_i, ARGV[2].to_i, ARGV[3].to_i)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment