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
*/


// The location you've extracted the file to:
f = read["file:/home/eliasen/builds/timezones/combined.json"]
println["File read."]
b = parseJSON[f]
println["JSON parsed."]

// 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"]
g.write["timezones.svg", 1920, undef]
println["Writing svgz"]
g.write["timezones.svgz", 1920, undef]
println["Writing HTML5"]
g.write["timezones.html", 1920, undef]
println["Writing png"]
g.write["timezones.png", 1920, undef]
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
   first = true
   for [x, y] = outer   // Draw the outer polygon
   {
      if first
      {
         ret.moveTo[x, -y]
         first = false
      } else
         ret.addPoint[x, -y]
   }
   ret.close[]

   for inner = slice[coordinates, 1, undef]  // Draw all the inner polygons
   {
      first = true
      for [x, y] = inner
      {
         if first
         {
            ret.moveTo[x, -y]
            first = false
         } else
            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 17591 days, 17 hours, 46 minutes ago.