Download or view mapTimezones.frink in plain text format
/** This program draws a map of the world's timezones. It requires the GeoJSON
file from:
https://github.com/evansiroky/timezone-boundary-builder/releases
specifically, you want to download the file
timezones-with-oceans.geojson.zip
and extract it, giving the file
combined-with-oceans.json
The GeoJSON format is defined in RFC 7946:
https://tools.ietf.org/html/rfc7946
*/
// This allows us to attach visualvm for profiling
//input=input["Press start", ""]
// The location you've extracted the file to:
s1 = now[]
s = now[]
println["Reading file."]
f = read["file:/home/eliasen/builds/timezones/combined-with-oceans.json"]
e = now[]
println["Finished in " + format[e-s, "s", 1] + "\n"]
s = now[]
b = parseJSON[f]
println["JSON parsed."]
e = now[]
println["Finished in " + format[e-s, "s", 1] + "\n"]
// The "type" key indicates what the top-level container is, hopefully a
// FeatureCollection
filetype = b@"type"
println["This file contains a $filetype"]
g = new graphics
g.font["SansSerif", 2]
glabels = new graphics
glabels.font["SansSerif", 1]
if filetype == "FeatureCollection"
plotFeatureCollection[b, g, glabels]
else
if filetype == "Feature"
plotFeature[b, g, glabels]
g.color[0,0,0]
g.add[glabels]
println["Writing svg"]
s=now[]
g.write["timezones.svg", 1920, undef]
e=now[]
println["Finished in " + format[e-s, "s", 1] + "\n"]
println["Writing svgz"]
s=now[]
g.write["timezones.svgz", 1920, undef]
e=now[]
println["Finished in " + format[e-s, "s", 1] + "\n"]
println["Writing HTML5"]
s=now[]
g.write["timezones.html", 1920, undef]
e=now[]
println["Finished in " + format[e-s, "s", 1] + "\n"]
println["Writing png"]
s=now[]
g.write["timezones.png", 1920, undef]
e=now[]
println["Finished in " + format[e-s, "s", 1] + "\n"]
e1 = now[]
println["Total time: " + format[e1-s1, "s", 1] + "\n"]
g.show[]
/** This plots a FeatureCollection, whose "features" key should contain an
array of Feature objects. */
plotFeatureCollection[fc, g is graphics, glabels is graphics] :=
{
for feature = fc@"features"
plotFeature[feature, g, glabels]
}
/** This plots a Feature object. A Feature has a "properties" key that
contains a dictionary that describes stuff (in this case, the "tzid" field
may be the only member,) and a key called "geometry" which contains the
object (in this case, a Polygon or MultiPolygon.)
*/
plotFeature[feature, g is graphics, glabels is graphics] :=
{
for [key, value] = feature
{
print["$key:\t"]
if key == "type" // This is a string, hopefully "Feature"
print[value]
// This contains a dictionary of key-value pairs. We most likely want
// the "tzid" pair
if key == "properties"
print["tzid: " + value@"tzid"]
g.color[randomFloat[0,1], randomFloat[0,1], randomFloat[0,1]]
// A Feature has a key called "geometry" which describes the type
if key == "geometry"
{
type = value@"type"
print[type]
gsub = undef
if (type == "Polygon")
{
coordinates = value@"coordinates"
gsub = makePolygon[coordinates]
} else
if (type == "MultiPolygon")
{
coordinates = value@"coordinates"
gsub = makeMultiPolygon[coordinates]
} else
println["Unknown type $type"]
if gsub != undef
{
g.add[gsub]
props = feature@"properties"
if props != undef
{
tzid = props@"tzid"
if tzid != undef
{
tzid =~ %s/\//\n/g // Split on slash
if type[gsub] == "Poly"
{
[cx, cy] = gsub.getCentroid[]
glabels.text[tzid, cx, cy]
} else
{
[left, top, right, bottom] = getBoundingBox[gsub]
glabels.text[tzid, (right-left)/2 + left, (bottom-top)/2 + top]
}
}
}
}
}
println[]
}
println[]
}
/** Make a "polygon", given an object containing a GeoJSON coordinates array.
In the GeoJSON specification, a "polygon" may actually be an array of
concentric disconnected polygons with the first one being a surrounding
polygon and the latter ones being "holes" in this object. If there are
no holes, then this is returned as a Polygon object, otherwise as a
GeneralPath.
*/
makePolygon[coordinates] :=
{
length = length[coordinates]
// If length == 1, this can be a simple polygon with no holes
if length == 1
{
ret = new filledPolygon
for [x,y] = coordinates@0
ret.addPoint[x, -y]
return ret
}
// Otherwise, this is a complex GeneralPath with holes
ret = new filledGeneralPath
outer = coordinates@0
for [x, y] = outer // Draw the outer polygon
ret.addPoint[x, -y]
ret.close[]
for inner = slice[coordinates, 1, undef] // Draw all the inner polygons
{
for [x, y] = inner
ret.addPoint[x, -y]
ret.close[]
}
return ret
}
// This draws a set of polygons and returns it as a graphics.
makeMultiPolygon[coordinates] :=
{
g = new graphics
for polygon = coordinates
g.add[makePolygon[polygon]]
return g
}
Download or view mapTimezones.frink in plain text format
This is a program written in the programming language Frink.
For more information, view the Frink
Documentation or see More Sample Frink Programs.
Alan Eliasen, eliasen@mindspring.com