mapTimezones.frink

View or download 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

    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.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]

if filetype == "FeatureCollection"
   plotFeatureCollection[b, g]
else
   if filetype == "Feature"
      plotFeature[b, g]


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] :=
{
   for feature = fc@"features"
      plotFeature[feature, g]
}

/** 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] :=
{
   for [key, value] = feature
   {
      print["$key:\t"]
      if key == "type"   // This is a string, hopefully "Feature"
         print[value]
      
      if key == "properties" // This contains a dictionary of key-value
         // pairs.  We most likely want the "tzid" pair
      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]
         if (type == "Polygon")
         {
            coordinates = value@"coordinates"
            g.add[makePolygon[coordinates]]
         } else
         if (type == "MultiPolygon")
         {
            coordinates = value@"coordinates"
            g.add[makeMultiPolygon[coordinates]]
         } else
         println["Unknown type $type"]
      }

      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
}


View or download 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 was born 18011 days, 14 hours, 54 minutes ago.