/** This program draws a map of U.S. states. It requires the GeoJSON file from: https://web.archive.org/web/20130615162524if_/http://eric.clst.org:80/wupl/Stuff/gz_2010_us_040_00_500k.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", ""] s1 = now[] s = now[] println["Reading file."] // The location you've extracted the file to: f = read["file:/home/eliasen/prog/frink/samples/gz_2010_us_040_00_500k.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["states.svg", 1920, undef] e=now[] println["Finished in " + format[e-s, "s", 1] + "\n"] println["Writing svgz"] s=now[] g.write["states.svgz", 1920, undef] e=now[] println["Finished in " + format[e-s, "s", 1] + "\n"] println["Writing HTML5"] s=now[] g.write["states.html", 1920, undef] e=now[] println["Finished in " + format[e-s, "s", 1] + "\n"] println["Writing png"] s=now[] g.write["states.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 "NAME" pair { print["NAME: " + value@"NAME" + "\t"] print["STATE: " + value@"STATE" + "\t"] } 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 { // This is a kludge to get the couple of Aleutian islands that are // west of 180 degrees west to not make the whole map wrap around if x > 0 x = x - 360 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 { // This is a kludge to get the couple of Aleutian islands that are // west of 180 degrees west to not make the whole map wrap around if x > 0 x = x - 360 ret.addPoint[x, -y] } ret.close[] for inner = slice[coordinates, 1, undef] // Draw all the inner polygons { for [x, y] = inner { // This is a kludge to get the couple of Aleutian islands that are // west of 180 degrees west to not make the whole map wrap around if x > 0 x = x - 360 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 }