Skip to content

Instantly share code, notes, and snippets.

@harrisoncramer
Last active November 24, 2017 21:05
Show Gist options
  • Save harrisoncramer/5444e43a1afd2f9e7898c800848c67ce to your computer and use it in GitHub Desktop.
Save harrisoncramer/5444e43a1afd2f9e7898c800848c67ce to your computer and use it in GitHub Desktop.
Color by Latitude
license: gpl-3.0
var colorbrewer={YlGn:{3:["#f7fcb9","#addd8e","#31a354"],4:["#ffffcc","#c2e699","#78c679","#238443"],5:["#ffffcc","#c2e699","#78c679","#31a354","#006837"],6:["#ffffcc","#d9f0a3","#addd8e","#78c679","#31a354","#006837"],7:["#ffffcc","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"],8:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"],9:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"]},YlGnBu:{3:["#edf8b1","#7fcdbb","#2c7fb8"],4:["#ffffcc","#a1dab4","#41b6c4","#225ea8"],5:["#ffffcc","#a1dab4","#41b6c4","#2c7fb8","#253494"],6:["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"],7:["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"],8:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"],9:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"]},GnBu:{3:["#e0f3db","#a8ddb5","#43a2ca"],4:["#f0f9e8","#bae4bc","#7bccc4","#2b8cbe"],5:["#f0f9e8","#bae4bc","#7bccc4","#43a2ca","#0868ac"],6:["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#43a2ca","#0868ac"],7:["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"],8:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"],9:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"]},BuGn:{3:["#e5f5f9","#99d8c9","#2ca25f"],4:["#edf8fb","#b2e2e2","#66c2a4","#238b45"],5:["#edf8fb","#b2e2e2","#66c2a4","#2ca25f","#006d2c"],6:["#edf8fb","#ccece6","#99d8c9","#66c2a4","#2ca25f","#006d2c"],7:["#edf8fb","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"],8:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"],9:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"]},PuBuGn:{3:["#ece2f0","#a6bddb","#1c9099"],4:["#f6eff7","#bdc9e1","#67a9cf","#02818a"],5:["#f6eff7","#bdc9e1","#67a9cf","#1c9099","#016c59"],6:["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#1c9099","#016c59"],7:["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"],8:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"],9:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"]},PuBu:{3:["#ece7f2","#a6bddb","#2b8cbe"],4:["#f1eef6","#bdc9e1","#74a9cf","#0570b0"],5:["#f1eef6","#bdc9e1","#74a9cf","#2b8cbe","#045a8d"],6:["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#2b8cbe","#045a8d"],7:["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"],8:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"],9:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"]},BuPu:{3:["#e0ecf4","#9ebcda","#8856a7"],4:["#edf8fb","#b3cde3","#8c96c6","#88419d"],5:["#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"],6:["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8856a7","#810f7c"],7:["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"],8:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"],9:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"]},RdPu:{3:["#fde0dd","#fa9fb5","#c51b8a"],4:["#feebe2","#fbb4b9","#f768a1","#ae017e"],5:["#feebe2","#fbb4b9","#f768a1","#c51b8a","#7a0177"],6:["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#c51b8a","#7a0177"],7:["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"],8:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"],9:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"]},PuRd:{3:["#e7e1ef","#c994c7","#dd1c77"],4:["#f1eef6","#d7b5d8","#df65b0","#ce1256"],5:["#f1eef6","#d7b5d8","#df65b0","#dd1c77","#980043"],6:["#f1eef6","#d4b9da","#c994c7","#df65b0","#dd1c77","#980043"],7:["#f1eef6","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"],8:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"],9:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"]},OrRd:{3:["#fee8c8","#fdbb84","#e34a33"],4:["#fef0d9","#fdcc8a","#fc8d59","#d7301f"],5:["#fef0d9","#fdcc8a","#fc8d59","#e34a33","#b30000"],6:["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#e34a33","#b30000"],7:["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"],8:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"],9:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"]},YlOrRd:{3:["#ffeda0","#feb24c","#f03b20"],4:["#ffffb2","#fecc5c","#fd8d3c","#e31a1c"],5:["#ffffb2","#fecc5c","#fd8d3c","#f03b20","#bd0026"],6:["#ffffb2","#fed976","#feb24c","#fd8d3c","#f03b20","#bd0026"],7:["#ffffb2","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"],8:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"],9:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"]},YlOrBr:{3:["#fff7bc","#fec44f","#d95f0e"],4:["#ffffd4","#fed98e","#fe9929","#cc4c02"],5:["#ffffd4","#fed98e","#fe9929","#d95f0e","#993404"],6:["#ffffd4","#fee391","#fec44f","#fe9929","#d95f0e","#993404"],7:["#ffffd4","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"],8:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"],9:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"]},Purples:{3:["#efedf5","#bcbddc","#756bb1"],4:["#f2f0f7","#cbc9e2","#9e9ac8","#6a51a3"],5:["#f2f0f7","#cbc9e2","#9e9ac8","#756bb1","#54278f"],6:["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#756bb1","#54278f"],7:["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"],8:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"],9:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"]},Blues:{3:["#deebf7","#9ecae1","#3182bd"],4:["#eff3ff","#bdd7e7","#6baed6","#2171b5"],5:["#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c"],6:["#eff3ff","#c6dbef","#9ecae1","#6baed6","#3182bd","#08519c"],7:["#eff3ff","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"],8:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"],9:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"]},Greens:{3:["#e5f5e0","#a1d99b","#31a354"],4:["#edf8e9","#bae4b3","#74c476","#238b45"],5:["#edf8e9","#bae4b3","#74c476","#31a354","#006d2c"],6:["#edf8e9","#c7e9c0","#a1d99b","#74c476","#31a354","#006d2c"],7:["#edf8e9","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"],8:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"],9:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"]},Oranges:{3:["#fee6ce","#fdae6b","#e6550d"],4:["#feedde","#fdbe85","#fd8d3c","#d94701"],5:["#feedde","#fdbe85","#fd8d3c","#e6550d","#a63603"],6:["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#e6550d","#a63603"],7:["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"],8:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"],9:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"]},Reds:{3:["#fee0d2","#fc9272","#de2d26"],4:["#fee5d9","#fcae91","#fb6a4a","#cb181d"],5:["#fee5d9","#fcae91","#fb6a4a","#de2d26","#a50f15"],6:["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#de2d26","#a50f15"],7:["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"],8:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"],9:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"]},Greys:{3:["#f0f0f0","#bdbdbd","#636363"],4:["#f7f7f7","#cccccc","#969696","#525252"],5:["#f7f7f7","#cccccc","#969696","#636363","#252525"],6:["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#636363","#252525"],7:["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"],8:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"],9:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"]},PuOr:{3:["#f1a340","#f7f7f7","#998ec3"],4:["#e66101","#fdb863","#b2abd2","#5e3c99"],5:["#e66101","#fdb863","#f7f7f7","#b2abd2","#5e3c99"],6:["#b35806","#f1a340","#fee0b6","#d8daeb","#998ec3","#542788"],7:["#b35806","#f1a340","#fee0b6","#f7f7f7","#d8daeb","#998ec3","#542788"],8:["#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788"],9:["#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788"],10:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],11:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"]},BrBG:{3:["#d8b365","#f5f5f5","#5ab4ac"],4:["#a6611a","#dfc27d","#80cdc1","#018571"],5:["#a6611a","#dfc27d","#f5f5f5","#80cdc1","#018571"],6:["#8c510a","#d8b365","#f6e8c3","#c7eae5","#5ab4ac","#01665e"],7:["#8c510a","#d8b365","#f6e8c3","#f5f5f5","#c7eae5","#5ab4ac","#01665e"],8:["#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e"],9:["#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e"],10:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],11:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"]},PRGn:{3:["#af8dc3","#f7f7f7","#7fbf7b"],4:["#7b3294","#c2a5cf","#a6dba0","#008837"],5:["#7b3294","#c2a5cf","#f7f7f7","#a6dba0","#008837"],6:["#762a83","#af8dc3","#e7d4e8","#d9f0d3","#7fbf7b","#1b7837"],7:["#762a83","#af8dc3","#e7d4e8","#f7f7f7","#d9f0d3","#7fbf7b","#1b7837"],8:["#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837"],9:["#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837"],10:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],11:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"]},PiYG:{3:["#e9a3c9","#f7f7f7","#a1d76a"],4:["#d01c8b","#f1b6da","#b8e186","#4dac26"],5:["#d01c8b","#f1b6da","#f7f7f7","#b8e186","#4dac26"],6:["#c51b7d","#e9a3c9","#fde0ef","#e6f5d0","#a1d76a","#4d9221"],7:["#c51b7d","#e9a3c9","#fde0ef","#f7f7f7","#e6f5d0","#a1d76a","#4d9221"],8:["#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221"],9:["#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221"],10:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],11:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"]},RdBu:{3:["#ef8a62","#f7f7f7","#67a9cf"],4:["#ca0020","#f4a582","#92c5de","#0571b0"],5:["#ca0020","#f4a582","#f7f7f7","#92c5de","#0571b0"],6:["#b2182b","#ef8a62","#fddbc7","#d1e5f0","#67a9cf","#2166ac"],7:["#b2182b","#ef8a62","#fddbc7","#f7f7f7","#d1e5f0","#67a9cf","#2166ac"],8:["#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac"],9:["#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac"],10:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],11:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"]},RdGy:{3:["#ef8a62","#ffffff","#999999"],4:["#ca0020","#f4a582","#bababa","#404040"],5:["#ca0020","#f4a582","#ffffff","#bababa","#404040"],6:["#b2182b","#ef8a62","#fddbc7","#e0e0e0","#999999","#4d4d4d"],7:["#b2182b","#ef8a62","#fddbc7","#ffffff","#e0e0e0","#999999","#4d4d4d"],8:["#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d"],9:["#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d"],10:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],11:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"]},RdYlBu:{3:["#fc8d59","#ffffbf","#91bfdb"],4:["#d7191c","#fdae61","#abd9e9","#2c7bb6"],5:["#d7191c","#fdae61","#ffffbf","#abd9e9","#2c7bb6"],6:["#d73027","#fc8d59","#fee090","#e0f3f8","#91bfdb","#4575b4"],7:["#d73027","#fc8d59","#fee090","#ffffbf","#e0f3f8","#91bfdb","#4575b4"],8:["#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4"],9:["#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4"],10:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],11:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"]},Spectral:{3:["#fc8d59","#ffffbf","#99d594"],4:["#d7191c","#fdae61","#abdda4","#2b83ba"],5:["#d7191c","#fdae61","#ffffbf","#abdda4","#2b83ba"],6:["#d53e4f","#fc8d59","#fee08b","#e6f598","#99d594","#3288bd"],7:["#d53e4f","#fc8d59","#fee08b","#ffffbf","#e6f598","#99d594","#3288bd"],8:["#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd"],9:["#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd"],10:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],11:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"]},RdYlGn:{3:["#fc8d59","#ffffbf","#91cf60"],4:["#d7191c","#fdae61","#a6d96a","#1a9641"],5:["#d7191c","#fdae61","#ffffbf","#a6d96a","#1a9641"],6:["#d73027","#fc8d59","#fee08b","#d9ef8b","#91cf60","#1a9850"],7:["#d73027","#fc8d59","#fee08b","#ffffbf","#d9ef8b","#91cf60","#1a9850"],8:["#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850"],9:["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"],10:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],11:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"]},Accent:{3:["#7fc97f","#beaed4","#fdc086"],4:["#7fc97f","#beaed4","#fdc086","#ffff99"],5:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0"],6:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f"],7:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17"],8:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"]},Dark2:{3:["#1b9e77","#d95f02","#7570b3"],4:["#1b9e77","#d95f02","#7570b3","#e7298a"],5:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e"],6:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02"],7:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d"],8:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"]},Paired:{3:["#a6cee3","#1f78b4","#b2df8a"],4:["#a6cee3","#1f78b4","#b2df8a","#33a02c"],5:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99"],6:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c"],7:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f"],8:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00"],9:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6"],10:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a"],11:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99"],12:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"]},Pastel1:{3:["#fbb4ae","#b3cde3","#ccebc5"],4:["#fbb4ae","#b3cde3","#ccebc5","#decbe4"],5:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6"],6:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc"],7:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd"],8:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec"],9:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"]},Pastel2:{3:["#b3e2cd","#fdcdac","#cbd5e8"],4:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4"],5:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9"],6:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae"],7:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc"],8:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"]},Set1:{3:["#e41a1c","#377eb8","#4daf4a"],4:["#e41a1c","#377eb8","#4daf4a","#984ea3"],5:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00"],6:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33"],7:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628"],8:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf"],9:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"]},Set2:{3:["#66c2a5","#fc8d62","#8da0cb"],4:["#66c2a5","#fc8d62","#8da0cb","#e78ac3"],5:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854"],6:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f"],7:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494"],8:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"]},Set3:{3:["#8dd3c7","#ffffb3","#bebada"],4:["#8dd3c7","#ffffb3","#bebada","#fb8072"],5:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3"],6:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462"],7:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69"],8:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5"],9:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9"],10:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd"],11:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5"],12:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"]}};
<!doctype html>
<html>
<head>
<title>World TopoJSON</title>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="topojson.js"></script>
<script src="colorbrewer.v1.min.js"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<input type="range" min="-90" max="90" class="slider" value="0"/>
</body>
<script>
var width = 600
var height = 500
var xTranslate = -4475
var yTranslate = 2700
var scaleStart = 4000
// Create a more versatile promiseWrapper that can take multiple types of data, including geoJSON and CSV files. The XHR object is the request type (d3.json, d3.csv, etc) for processing the data, the d is the data string URL itself.
var promiseWrapper = (xhr, d) => new Promise(resolve => xhr(d, (p) => resolve(p)));
// Feed it the data, on resolve call the function to draw the map.
Promise.all([
promiseWrapper(d3.json,"world.topojson"),
promiseWrapper(d3.csv,"cities.csv")
]).then(resolve =>{
createMap(resolve[0],resolve[1],mergepoint)
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var mergepoint = document.getElementsByClassName("slider")["0"].value
function createMap(topoCountries, cities, mergepoint){
// console.log("TopoJSON Data: ", topoCountries)
// To format the topojson file to turn it into geoJSON features, we use the topojson.feature() method. It accepts the topojson data as the first argument, and then the accessor (.objects.countries) to get to the relevant features. The ".objects" method will be relevant for all topojson files, but the ".countries" is specific to this file.
var countries = topojson.feature(topoCountries, topoCountries.objects.countries)
// console.log("Formatted Data: ", countries)
// When the topoJSON data is formatted, the relevant coordinates are put inside the ".features" property of the geoJSON data, which we later access using "countries.features"
// console.log("JSON Coordinates: ", countries.features)
// Now, we can draw our map as we would a geoJSON file.
// 1. Define our projection
var projection = d3.geoMollweide()
.scale(120)
.translate([250,250])
.center([20,0])
// 2. Pass the projection to our geoPath function.
var geoPath = d3.geoPath().projection(projection)
// 3. Define coloring
var featureSize = d3.extent(countries.features, d => geoPath.area(d))
var countryColor = d3.scaleQuantize()
.domain(featureSize).range(colorbrewer.Greens[7]);
// 4. Append our data, draw the SVGs. As was previously mentioned, the data for our SVGs is accessed by the ".features" method of the countries array.
svg.selectAll("path.countries")
.data(countries.features)
.enter()
.append("path")
.attr("d", geoPath)
.classed("countries", true)
.style("fill", d => countryColor(geoPath.area(d)))
function mergeAt(latitude){
// We're working with hthe TopoJSON dataset. Filter for countries whose Centroid has a latitude coordinate greater than 0.
var filteredCountries = topoCountries.objects.countries.geometries
.filter(d => {
// Convert countries to GEOJSON using topojson.feature, find center.
var thisCenter = d3.geoCentroid(
topojson.feature(topoCountries, d))
// If latitude coordinate is greater than 0 (any point on country above latitude of 0) then return true.
return thisCenter[1] > latitude ? true : false;
})
d3.select("svg").append("g")
.datum(topojson.merge(topoCountries, filteredCountries))
.insert("path")
.attr("class", "merged")
.attr("d", geoPath)
}
mergeAt(mergepoint)
d3.select("input.slider").on("mousemove", function(d){
d3.selectAll("path").remove();
var mergepoint = this.value;
mergeAt(mergepoint);
createMap(topoCountries, cities, mergepoint)
})
}
</script>
</html>
.merged {
fill: red;
stroke: black;
}
.countries {
stroke: grey;
}
// https://github.com/topojson/topojson Version 3.0.2. Copyright 2017 Mike Bostock.
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.topojson = global.topojson || {})));
}(this, (function (exports) { 'use strict';
var identity = function(x) {
return x;
};
var transform = function(transform) {
if (transform == null) return identity;
var x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(input, i) {
if (!i) x0 = y0 = 0;
var j = 2, n = input.length, output = new Array(n);
output[0] = (x0 += input[0]) * kx + dx;
output[1] = (y0 += input[1]) * ky + dy;
while (j < n) output[j] = input[j], ++j;
return output;
};
};
var bbox = function(topology) {
var t = transform(topology.transform), key,
x0 = Infinity, y0 = x0, x1 = -x0, y1 = -x0;
function bboxPoint(p) {
p = t(p);
if (p[0] < x0) x0 = p[0];
if (p[0] > x1) x1 = p[0];
if (p[1] < y0) y0 = p[1];
if (p[1] > y1) y1 = p[1];
}
function bboxGeometry(o) {
switch (o.type) {
case "GeometryCollection": o.geometries.forEach(bboxGeometry); break;
case "Point": bboxPoint(o.coordinates); break;
case "MultiPoint": o.coordinates.forEach(bboxPoint); break;
}
}
topology.arcs.forEach(function(arc) {
var i = -1, n = arc.length, p;
while (++i < n) {
p = t(arc[i], i);
if (p[0] < x0) x0 = p[0];
if (p[0] > x1) x1 = p[0];
if (p[1] < y0) y0 = p[1];
if (p[1] > y1) y1 = p[1];
}
});
for (key in topology.objects) {
bboxGeometry(topology.objects[key]);
}
return [x0, y0, x1, y1];
};
var reverse = function(array, n) {
var t, j = array.length, i = j - n;
while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
};
var feature = function(topology, o) {
return o.type === "GeometryCollection"
? {type: "FeatureCollection", features: o.geometries.map(function(o) { return feature$1(topology, o); })}
: feature$1(topology, o);
};
function feature$1(topology, o) {
var id = o.id,
bbox = o.bbox,
properties = o.properties == null ? {} : o.properties,
geometry = object(topology, o);
return id == null && bbox == null ? {type: "Feature", properties: properties, geometry: geometry}
: bbox == null ? {type: "Feature", id: id, properties: properties, geometry: geometry}
: {type: "Feature", id: id, bbox: bbox, properties: properties, geometry: geometry};
}
function object(topology, o) {
var transformPoint = transform(topology.transform),
arcs = topology.arcs;
function arc(i, points) {
if (points.length) points.pop();
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length; k < n; ++k) {
points.push(transformPoint(a[k], k));
}
if (i < 0) reverse(points, n);
}
function point(p) {
return transformPoint(p);
}
function line(arcs) {
var points = [];
for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
if (points.length < 2) points.push(points[0]); // This should never happen per the specification.
return points;
}
function ring(arcs) {
var points = line(arcs);
while (points.length < 4) points.push(points[0]); // This may happen if an arc has only two points.
return points;
}
function polygon(arcs) {
return arcs.map(ring);
}
function geometry(o) {
var type = o.type, coordinates;
switch (type) {
case "GeometryCollection": return {type: type, geometries: o.geometries.map(geometry)};
case "Point": coordinates = point(o.coordinates); break;
case "MultiPoint": coordinates = o.coordinates.map(point); break;
case "LineString": coordinates = line(o.arcs); break;
case "MultiLineString": coordinates = o.arcs.map(line); break;
case "Polygon": coordinates = polygon(o.arcs); break;
case "MultiPolygon": coordinates = o.arcs.map(polygon); break;
default: return null;
}
return {type: type, coordinates: coordinates};
}
return geometry(o);
}
var stitch = function(topology, arcs) {
var stitchedArcs = {},
fragmentByStart = {},
fragmentByEnd = {},
fragments = [],
emptyIndex = -1;
// Stitch empty arcs first, since they may be subsumed by other arcs.
arcs.forEach(function(i, j) {
var arc = topology.arcs[i < 0 ? ~i : i], t;
if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
}
});
arcs.forEach(function(i) {
var e = ends(i),
start = e[0],
end = e[1],
f, g;
if (f = fragmentByEnd[start]) {
delete fragmentByEnd[f.end];
f.push(i);
f.end = end;
if (g = fragmentByStart[end]) {
delete fragmentByStart[g.start];
var fg = g === f ? f : f.concat(g);
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByStart[end]) {
delete fragmentByStart[f.start];
f.unshift(i);
f.start = start;
if (g = fragmentByEnd[start]) {
delete fragmentByEnd[g.end];
var gf = g === f ? f : g.concat(f);
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else {
f = [i];
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
}
});
function ends(i) {
var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;
if (topology.transform) p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; });
else p1 = arc[arc.length - 1];
return i < 0 ? [p1, p0] : [p0, p1];
}
function flush(fragmentByEnd, fragmentByStart) {
for (var k in fragmentByEnd) {
var f = fragmentByEnd[k];
delete fragmentByStart[f.start];
delete f.start;
delete f.end;
f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });
fragments.push(f);
}
}
flush(fragmentByEnd, fragmentByStart);
flush(fragmentByStart, fragmentByEnd);
arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); });
return fragments;
};
var mesh = function(topology) {
return object(topology, meshArcs.apply(this, arguments));
};
function meshArcs(topology, object$$1, filter) {
var arcs, i, n;
if (arguments.length > 1) arcs = extractArcs(topology, object$$1, filter);
else for (i = 0, arcs = new Array(n = topology.arcs.length); i < n; ++i) arcs[i] = i;
return {type: "MultiLineString", arcs: stitch(topology, arcs)};
}
function extractArcs(topology, object$$1, filter) {
var arcs = [],
geomsByArc = [],
geom;
function extract0(i) {
var j = i < 0 ? ~i : i;
(geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom});
}
function extract1(arcs) {
arcs.forEach(extract0);
}
function extract2(arcs) {
arcs.forEach(extract1);
}
function extract3(arcs) {
arcs.forEach(extract2);
}
function geometry(o) {
switch (geom = o, o.type) {
case "GeometryCollection": o.geometries.forEach(geometry); break;
case "LineString": extract1(o.arcs); break;
case "MultiLineString": case "Polygon": extract2(o.arcs); break;
case "MultiPolygon": extract3(o.arcs); break;
}
}
geometry(object$$1);
geomsByArc.forEach(filter == null
? function(geoms) { arcs.push(geoms[0].i); }
: function(geoms) { if (filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i); });
return arcs;
}
function planarRingArea(ring) {
var i = -1, n = ring.length, a, b = ring[n - 1], area = 0;
while (++i < n) a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0];
return Math.abs(area); // Note: doubled area!
}
var merge = function(topology) {
return object(topology, mergeArcs.apply(this, arguments));
};
function mergeArcs(topology, objects) {
var polygonsByArc = {},
polygons = [],
groups = [];
objects.forEach(geometry);
function geometry(o) {
switch (o.type) {
case "GeometryCollection": o.geometries.forEach(geometry); break;
case "Polygon": extract(o.arcs); break;
case "MultiPolygon": o.arcs.forEach(extract); break;
}
}
function extract(polygon) {
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
(polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
});
});
polygons.push(polygon);
}
function area(ring) {
return planarRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]);
}
polygons.forEach(function(polygon) {
if (!polygon._) {
var group = [],
neighbors = [polygon];
polygon._ = 1;
groups.push(group);
while (polygon = neighbors.pop()) {
group.push(polygon);
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
polygonsByArc[arc < 0 ? ~arc : arc].forEach(function(polygon) {
if (!polygon._) {
polygon._ = 1;
neighbors.push(polygon);
}
});
});
});
}
}
});
polygons.forEach(function(polygon) {
delete polygon._;
});
return {
type: "MultiPolygon",
arcs: groups.map(function(polygons) {
var arcs = [], n;
// Extract the exterior (unique) arcs.
polygons.forEach(function(polygon) {
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
if (polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
arcs.push(arc);
}
});
});
});
// Stitch the arcs into one or more rings.
arcs = stitch(topology, arcs);
// If more than one ring is returned,
// at most one of these rings can be the exterior;
// choose the one with the greatest absolute area.
if ((n = arcs.length) > 1) {
for (var i = 1, k = area(arcs[0]), ki, t; i < n; ++i) {
if ((ki = area(arcs[i])) > k) {
t = arcs[0], arcs[0] = arcs[i], arcs[i] = t, k = ki;
}
}
}
return arcs;
})
};
}
var bisect = function(a, x) {
var lo = 0, hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (a[mid] < x) lo = mid + 1;
else hi = mid;
}
return lo;
};
var neighbors = function(objects) {
var indexesByArc = {}, // arc index -> array of object indexes
neighbors = objects.map(function() { return []; });
function line(arcs, i) {
arcs.forEach(function(a) {
if (a < 0) a = ~a;
var o = indexesByArc[a];
if (o) o.push(i);
else indexesByArc[a] = [i];
});
}
function polygon(arcs, i) {
arcs.forEach(function(arc) { line(arc, i); });
}
function geometry(o, i) {
if (o.type === "GeometryCollection") o.geometries.forEach(function(o) { geometry(o, i); });
else if (o.type in geometryType) geometryType[o.type](o.arcs, i);
}
var geometryType = {
LineString: line,
MultiLineString: polygon,
Polygon: polygon,
MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); }
};
objects.forEach(geometry);
for (var i in indexesByArc) {
for (var indexes = indexesByArc[i], m = indexes.length, j = 0; j < m; ++j) {
for (var k = j + 1; k < m; ++k) {
var ij = indexes[j], ik = indexes[k], n;
if ((n = neighbors[ij])[i = bisect(n, ik)] !== ik) n.splice(i, 0, ik);
if ((n = neighbors[ik])[i = bisect(n, ij)] !== ij) n.splice(i, 0, ij);
}
}
}
return neighbors;
};
var untransform = function(transform) {
if (transform == null) return identity;
var x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(input, i) {
if (!i) x0 = y0 = 0;
var j = 2,
n = input.length,
output = new Array(n),
x1 = Math.round((input[0] - dx) / kx),
y1 = Math.round((input[1] - dy) / ky);
output[0] = x1 - x0, x0 = x1;
output[1] = y1 - y0, y0 = y1;
while (j < n) output[j] = input[j], ++j;
return output;
};
};
var quantize = function(topology, transform) {
if (topology.transform) throw new Error("already quantized");
if (!transform || !transform.scale) {
if (!((n = Math.floor(transform)) >= 2)) throw new Error("n must be \u22652");
box = topology.bbox || bbox(topology);
var x0 = box[0], y0 = box[1], x1 = box[2], y1 = box[3], n;
transform = {scale: [x1 - x0 ? (x1 - x0) / (n - 1) : 1, y1 - y0 ? (y1 - y0) / (n - 1) : 1], translate: [x0, y0]};
} else {
box = topology.bbox;
}
var t = untransform(transform), box, key, inputs = topology.objects, outputs = {};
function quantizePoint(point) {
return t(point);
}
function quantizeGeometry(input) {
var output;
switch (input.type) {
case "GeometryCollection": output = {type: "GeometryCollection", geometries: input.geometries.map(quantizeGeometry)}; break;
case "Point": output = {type: "Point", coordinates: quantizePoint(input.coordinates)}; break;
case "MultiPoint": output = {type: "MultiPoint", coordinates: input.coordinates.map(quantizePoint)}; break;
default: return input;
}
if (input.id != null) output.id = input.id;
if (input.bbox != null) output.bbox = input.bbox;
if (input.properties != null) output.properties = input.properties;
return output;
}
function quantizeArc(input) {
var i = 0, j = 1, n = input.length, p, output = new Array(n); // pessimistic
output[0] = t(input[0], 0);
while (++i < n) if ((p = t(input[i], i))[0] || p[1]) output[j++] = p; // non-coincident points
if (j === 1) output[j++] = [0, 0]; // an arc must have at least two points
output.length = j;
return output;
}
for (key in inputs) outputs[key] = quantizeGeometry(inputs[key]);
return {
type: "Topology",
bbox: box,
transform: transform,
objects: outputs,
arcs: topology.arcs.map(quantizeArc)
};
};
// Computes the bounding box of the specified hash of GeoJSON objects.
var bounds = function(objects) {
var x0 = Infinity,
y0 = Infinity,
x1 = -Infinity,
y1 = -Infinity;
function boundGeometry(geometry) {
if (geometry != null && boundGeometryType.hasOwnProperty(geometry.type)) boundGeometryType[geometry.type](geometry);
}
var boundGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(boundGeometry); },
Point: function(o) { boundPoint(o.coordinates); },
MultiPoint: function(o) { o.coordinates.forEach(boundPoint); },
LineString: function(o) { boundLine(o.arcs); },
MultiLineString: function(o) { o.arcs.forEach(boundLine); },
Polygon: function(o) { o.arcs.forEach(boundLine); },
MultiPolygon: function(o) { o.arcs.forEach(boundMultiLine); }
};
function boundPoint(coordinates) {
var x = coordinates[0],
y = coordinates[1];
if (x < x0) x0 = x;
if (x > x1) x1 = x;
if (y < y0) y0 = y;
if (y > y1) y1 = y;
}
function boundLine(coordinates) {
coordinates.forEach(boundPoint);
}
function boundMultiLine(coordinates) {
coordinates.forEach(boundLine);
}
for (var key in objects) {
boundGeometry(objects[key]);
}
return x1 >= x0 && y1 >= y0 ? [x0, y0, x1, y1] : undefined;
};
var hashset = function(size, hash, equal, type, empty) {
if (arguments.length === 3) {
type = Array;
empty = null;
}
var store = new type(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
mask = size - 1;
for (var i = 0; i < size; ++i) {
store[i] = empty;
}
function add(value) {
var index = hash(value) & mask,
match = store[index],
collisions = 0;
while (match != empty) {
if (equal(match, value)) return true;
if (++collisions >= size) throw new Error("full hashset");
match = store[index = (index + 1) & mask];
}
store[index] = value;
return true;
}
function has(value) {
var index = hash(value) & mask,
match = store[index],
collisions = 0;
while (match != empty) {
if (equal(match, value)) return true;
if (++collisions >= size) break;
match = store[index = (index + 1) & mask];
}
return false;
}
function values() {
var values = [];
for (var i = 0, n = store.length; i < n; ++i) {
var match = store[i];
if (match != empty) values.push(match);
}
return values;
}
return {
add: add,
has: has,
values: values
};
};
var hashmap = function(size, hash, equal, keyType, keyEmpty, valueType) {
if (arguments.length === 3) {
keyType = valueType = Array;
keyEmpty = null;
}
var keystore = new keyType(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
valstore = new valueType(size),
mask = size - 1;
for (var i = 0; i < size; ++i) {
keystore[i] = keyEmpty;
}
function set(key, value) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index] = value;
if (++collisions >= size) throw new Error("full hashmap");
matchKey = keystore[index = (index + 1) & mask];
}
keystore[index] = key;
valstore[index] = value;
return value;
}
function maybeSet(key, value) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index];
if (++collisions >= size) throw new Error("full hashmap");
matchKey = keystore[index = (index + 1) & mask];
}
keystore[index] = key;
valstore[index] = value;
return value;
}
function get(key, missingValue) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index];
if (++collisions >= size) break;
matchKey = keystore[index = (index + 1) & mask];
}
return missingValue;
}
function keys() {
var keys = [];
for (var i = 0, n = keystore.length; i < n; ++i) {
var matchKey = keystore[i];
if (matchKey != keyEmpty) keys.push(matchKey);
}
return keys;
}
return {
set: set,
maybeSet: maybeSet, // set if unset
get: get,
keys: keys
};
};
var equalPoint = function(pointA, pointB) {
return pointA[0] === pointB[0] && pointA[1] === pointB[1];
};
// TODO if quantized, use simpler Int32 hashing?
var buffer = new ArrayBuffer(16);
var uints = new Uint32Array(buffer);
var hashPoint = function(point) {
var hash = uints[0] ^ uints[1];
hash = hash << 5 ^ hash >> 7 ^ uints[2] ^ uints[3];
return hash & 0x7fffffff;
};
// Given an extracted (pre-)topology, identifies all of the junctions. These are
// the points at which arcs (lines or rings) will need to be cut so that each
// arc is represented uniquely.
//
// A junction is a point where at least one arc deviates from another arc going
// through the same point. For example, consider the point B. If there is a arc
// through ABC and another arc through CBA, then B is not a junction because in
// both cases the adjacent point pairs are {A,C}. However, if there is an
// additional arc ABD, then {A,D} != {A,C}, and thus B becomes a junction.
//
// For a closed ring ABCA, the first point A’s adjacent points are the second
// and last point {B,C}. For a line, the first and last point are always
// considered junctions, even if the line is closed; this ensures that a closed
// line is never rotated.
var join = function(topology) {
var coordinates = topology.coordinates,
lines = topology.lines,
rings = topology.rings,
indexes = index(),
visitedByIndex = new Int32Array(coordinates.length),
leftByIndex = new Int32Array(coordinates.length),
rightByIndex = new Int32Array(coordinates.length),
junctionByIndex = new Int8Array(coordinates.length),
junctionCount = 0, // upper bound on number of junctions
i, n,
previousIndex,
currentIndex,
nextIndex;
for (i = 0, n = coordinates.length; i < n; ++i) {
visitedByIndex[i] = leftByIndex[i] = rightByIndex[i] = -1;
}
for (i = 0, n = lines.length; i < n; ++i) {
var line = lines[i],
lineStart = line[0],
lineEnd = line[1];
currentIndex = indexes[lineStart];
nextIndex = indexes[++lineStart];
++junctionCount, junctionByIndex[currentIndex] = 1; // start
while (++lineStart <= lineEnd) {
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[lineStart]);
}
++junctionCount, junctionByIndex[nextIndex] = 1; // end
}
for (i = 0, n = coordinates.length; i < n; ++i) {
visitedByIndex[i] = -1;
}
for (i = 0, n = rings.length; i < n; ++i) {
var ring = rings[i],
ringStart = ring[0] + 1,
ringEnd = ring[1];
previousIndex = indexes[ringEnd - 1];
currentIndex = indexes[ringStart - 1];
nextIndex = indexes[ringStart];
sequence(i, previousIndex, currentIndex, nextIndex);
while (++ringStart <= ringEnd) {
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[ringStart]);
}
}
function sequence(i, previousIndex, currentIndex, nextIndex) {
if (visitedByIndex[currentIndex] === i) return; // ignore self-intersection
visitedByIndex[currentIndex] = i;
var leftIndex = leftByIndex[currentIndex];
if (leftIndex >= 0) {
var rightIndex = rightByIndex[currentIndex];
if ((leftIndex !== previousIndex || rightIndex !== nextIndex)
&& (leftIndex !== nextIndex || rightIndex !== previousIndex)) {
++junctionCount, junctionByIndex[currentIndex] = 1;
}
} else {
leftByIndex[currentIndex] = previousIndex;
rightByIndex[currentIndex] = nextIndex;
}
}
function index() {
var indexByPoint = hashmap(coordinates.length * 1.4, hashIndex, equalIndex, Int32Array, -1, Int32Array),
indexes = new Int32Array(coordinates.length);
for (var i = 0, n = coordinates.length; i < n; ++i) {
indexes[i] = indexByPoint.maybeSet(i, i);
}
return indexes;
}
function hashIndex(i) {
return hashPoint(coordinates[i]);
}
function equalIndex(i, j) {
return equalPoint(coordinates[i], coordinates[j]);
}
visitedByIndex = leftByIndex = rightByIndex = null;
var junctionByPoint = hashset(junctionCount * 1.4, hashPoint, equalPoint), j;
// Convert back to a standard hashset by point for caller convenience.
for (i = 0, n = coordinates.length; i < n; ++i) {
if (junctionByIndex[j = indexes[i]]) {
junctionByPoint.add(coordinates[j]);
}
}
return junctionByPoint;
};
// Given an extracted (pre-)topology, cuts (or rotates) arcs so that all shared
// point sequences are identified. The topology can then be subsequently deduped
// to remove exact duplicate arcs.
var cut = function(topology) {
var junctions = join(topology),
coordinates = topology.coordinates,
lines = topology.lines,
rings = topology.rings,
next,
i, n;
for (i = 0, n = lines.length; i < n; ++i) {
var line = lines[i],
lineMid = line[0],
lineEnd = line[1];
while (++lineMid < lineEnd) {
if (junctions.has(coordinates[lineMid])) {
next = {0: lineMid, 1: line[1]};
line[1] = lineMid;
line = line.next = next;
}
}
}
for (i = 0, n = rings.length; i < n; ++i) {
var ring = rings[i],
ringStart = ring[0],
ringMid = ringStart,
ringEnd = ring[1],
ringFixed = junctions.has(coordinates[ringStart]);
while (++ringMid < ringEnd) {
if (junctions.has(coordinates[ringMid])) {
if (ringFixed) {
next = {0: ringMid, 1: ring[1]};
ring[1] = ringMid;
ring = ring.next = next;
} else { // For the first junction, we can rotate rather than cut.
rotateArray(coordinates, ringStart, ringEnd, ringEnd - ringMid);
coordinates[ringEnd] = coordinates[ringStart];
ringFixed = true;
ringMid = ringStart; // restart; we may have skipped junctions
}
}
}
}
return topology;
};
function rotateArray(array, start, end, offset) {
reverse$1(array, start, end);
reverse$1(array, start, start + offset);
reverse$1(array, start + offset, end);
}
function reverse$1(array, start, end) {
for (var mid = start + ((end-- - start) >> 1), t; start < mid; ++start, --end) {
t = array[start], array[start] = array[end], array[end] = t;
}
}
// Given a cut topology, combines duplicate arcs.
var dedup = function(topology) {
var coordinates = topology.coordinates,
lines = topology.lines, line,
rings = topology.rings, ring,
arcCount = lines.length + rings.length,
i, n;
delete topology.lines;
delete topology.rings;
// Count the number of (non-unique) arcs to initialize the hashmap safely.
for (i = 0, n = lines.length; i < n; ++i) {
line = lines[i]; while (line = line.next) ++arcCount;
}
for (i = 0, n = rings.length; i < n; ++i) {
ring = rings[i]; while (ring = ring.next) ++arcCount;
}
var arcsByEnd = hashmap(arcCount * 2 * 1.4, hashPoint, equalPoint),
arcs = topology.arcs = [];
for (i = 0, n = lines.length; i < n; ++i) {
line = lines[i];
do {
dedupLine(line);
} while (line = line.next);
}
for (i = 0, n = rings.length; i < n; ++i) {
ring = rings[i];
if (ring.next) { // arc is no longer closed
do {
dedupLine(ring);
} while (ring = ring.next);
} else {
dedupRing(ring);
}
}
function dedupLine(arc) {
var startPoint,
endPoint,
startArcs, startArc,
endArcs, endArc,
i, n;
// Does this arc match an existing arc in order?
if (startArcs = arcsByEnd.get(startPoint = coordinates[arc[0]])) {
for (i = 0, n = startArcs.length; i < n; ++i) {
startArc = startArcs[i];
if (equalLine(startArc, arc)) {
arc[0] = startArc[0];
arc[1] = startArc[1];
return;
}
}
}
// Does this arc match an existing arc in reverse order?
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[1]])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (reverseEqualLine(endArc, arc)) {
arc[1] = endArc[0];
arc[0] = endArc[1];
return;
}
}
}
if (startArcs) startArcs.push(arc); else arcsByEnd.set(startPoint, [arc]);
if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
arcs.push(arc);
}
function dedupRing(arc) {
var endPoint,
endArcs,
endArc,
i, n;
// Does this arc match an existing line in order, or reverse order?
// Rings are closed, so their start point and end point is the same.
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0]])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (equalRing(endArc, arc)) {
arc[0] = endArc[0];
arc[1] = endArc[1];
return;
}
if (reverseEqualRing(endArc, arc)) {
arc[0] = endArc[1];
arc[1] = endArc[0];
return;
}
}
}
// Otherwise, does this arc match an existing ring in order, or reverse order?
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0] + findMinimumOffset(arc)])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (equalRing(endArc, arc)) {
arc[0] = endArc[0];
arc[1] = endArc[1];
return;
}
if (reverseEqualRing(endArc, arc)) {
arc[0] = endArc[1];
arc[1] = endArc[0];
return;
}
}
}
if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
arcs.push(arc);
}
function equalLine(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1];
if (ia - ja !== ib - jb) return false;
for (; ia <= ja; ++ia, ++ib) if (!equalPoint(coordinates[ia], coordinates[ib])) return false;
return true;
}
function reverseEqualLine(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1];
if (ia - ja !== ib - jb) return false;
for (; ia <= ja; ++ia, --jb) if (!equalPoint(coordinates[ia], coordinates[jb])) return false;
return true;
}
function equalRing(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1],
n = ja - ia;
if (n !== jb - ib) return false;
var ka = findMinimumOffset(arcA),
kb = findMinimumOffset(arcB);
for (var i = 0; i < n; ++i) {
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[ib + (i + kb) % n])) return false;
}
return true;
}
function reverseEqualRing(arcA, arcB) {
var ia = arcA[0], ib = arcB[0],
ja = arcA[1], jb = arcB[1],
n = ja - ia;
if (n !== jb - ib) return false;
var ka = findMinimumOffset(arcA),
kb = n - findMinimumOffset(arcB);
for (var i = 0; i < n; ++i) {
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[jb - (i + kb) % n])) return false;
}
return true;
}
// Rings are rotated to a consistent, but arbitrary, start point.
// This is necessary to detect when a ring and a rotated copy are dupes.
function findMinimumOffset(arc) {
var start = arc[0],
end = arc[1],
mid = start,
minimum = mid,
minimumPoint = coordinates[mid];
while (++mid < end) {
var point = coordinates[mid];
if (point[0] < minimumPoint[0] || point[0] === minimumPoint[0] && point[1] < minimumPoint[1]) {
minimum = mid;
minimumPoint = point;
}
}
return minimum - start;
}
return topology;
};
// Given an array of arcs in absolute (but already quantized!) coordinates,
// converts to fixed-point delta encoding.
// This is a destructive operation that modifies the given arcs!
var delta = function(arcs) {
var i = -1,
n = arcs.length;
while (++i < n) {
var arc = arcs[i],
j = 0,
k = 1,
m = arc.length,
point = arc[0],
x0 = point[0],
y0 = point[1],
x1,
y1;
while (++j < m) {
point = arc[j], x1 = point[0], y1 = point[1];
if (x1 !== x0 || y1 !== y0) arc[k++] = [x1 - x0, y1 - y0], x0 = x1, y0 = y1;
}
if (k === 1) arc[k++] = [0, 0]; // Each arc must be an array of two or more positions.
arc.length = k;
}
return arcs;
};
// Extracts the lines and rings from the specified hash of geometry objects.
//
// Returns an object with three properties:
//
// * coordinates - shared buffer of [x, y] coordinates
// * lines - lines extracted from the hash, of the form [start, end]
// * rings - rings extracted from the hash, of the form [start, end]
//
// For each ring or line, start and end represent inclusive indexes into the
// coordinates buffer. For rings (and closed lines), coordinates[start] equals
// coordinates[end].
//
// For each line or polygon geometry in the input hash, including nested
// geometries as in geometry collections, the `coordinates` array is replaced
// with an equivalent `arcs` array that, for each line (for line string
// geometries) or ring (for polygon geometries), points to one of the above
// lines or rings.
var extract = function(objects) {
var index = -1,
lines = [],
rings = [],
coordinates = [];
function extractGeometry(geometry) {
if (geometry && extractGeometryType.hasOwnProperty(geometry.type)) extractGeometryType[geometry.type](geometry);
}
var extractGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(extractGeometry); },
LineString: function(o) { o.arcs = extractLine(o.arcs); },
MultiLineString: function(o) { o.arcs = o.arcs.map(extractLine); },
Polygon: function(o) { o.arcs = o.arcs.map(extractRing); },
MultiPolygon: function(o) { o.arcs = o.arcs.map(extractMultiRing); }
};
function extractLine(line) {
for (var i = 0, n = line.length; i < n; ++i) coordinates[++index] = line[i];
var arc = {0: index - n + 1, 1: index};
lines.push(arc);
return arc;
}
function extractRing(ring) {
for (var i = 0, n = ring.length; i < n; ++i) coordinates[++index] = ring[i];
var arc = {0: index - n + 1, 1: index};
rings.push(arc);
return arc;
}
function extractMultiRing(rings) {
return rings.map(extractRing);
}
for (var key in objects) {
extractGeometry(objects[key]);
}
return {
type: "Topology",
coordinates: coordinates,
lines: lines,
rings: rings,
objects: objects
};
};
// Given a hash of GeoJSON objects, returns a hash of GeoJSON geometry objects.
// Any null input geometry objects are represented as {type: null} in the output.
// Any feature.{id,properties,bbox} are transferred to the output geometry object.
// Each output geometry object is a shallow copy of the input (e.g., properties, coordinates)!
var geometry = function(inputs) {
var outputs = {}, key;
for (key in inputs) outputs[key] = geomifyObject(inputs[key]);
return outputs;
};
function geomifyObject(input) {
return input == null ? {type: null}
: (input.type === "FeatureCollection" ? geomifyFeatureCollection
: input.type === "Feature" ? geomifyFeature
: geomifyGeometry)(input);
}
function geomifyFeatureCollection(input) {
var output = {type: "GeometryCollection", geometries: input.features.map(geomifyFeature)};
if (input.bbox != null) output.bbox = input.bbox;
return output;
}
function geomifyFeature(input) {
var output = geomifyGeometry(input.geometry), key; // eslint-disable-line no-unused-vars
if (input.id != null) output.id = input.id;
if (input.bbox != null) output.bbox = input.bbox;
for (key in input.properties) { output.properties = input.properties; break; }
return output;
}
function geomifyGeometry(input) {
if (input == null) return {type: null};
var output = input.type === "GeometryCollection" ? {type: "GeometryCollection", geometries: input.geometries.map(geomifyGeometry)}
: input.type === "Point" || input.type === "MultiPoint" ? {type: input.type, coordinates: input.coordinates}
: {type: input.type, arcs: input.coordinates}; // TODO Check for unknown types?
if (input.bbox != null) output.bbox = input.bbox;
return output;
}
var prequantize = function(objects, bbox, n) {
var x0 = bbox[0],
y0 = bbox[1],
x1 = bbox[2],
y1 = bbox[3],
kx = x1 - x0 ? (n - 1) / (x1 - x0) : 1,
ky = y1 - y0 ? (n - 1) / (y1 - y0) : 1;
function quantizePoint(input) {
return [Math.round((input[0] - x0) * kx), Math.round((input[1] - y0) * ky)];
}
function quantizePoints(input, m) {
var i = -1,
j = 0,
n = input.length,
output = new Array(n), // pessimistic
pi,
px,
py,
x,
y;
while (++i < n) {
pi = input[i];
x = Math.round((pi[0] - x0) * kx);
y = Math.round((pi[1] - y0) * ky);
if (x !== px || y !== py) output[j++] = [px = x, py = y]; // non-coincident points
}
output.length = j;
while (j < m) j = output.push([output[0][0], output[0][1]]);
return output;
}
function quantizeLine(input) {
return quantizePoints(input, 2);
}
function quantizeRing(input) {
return quantizePoints(input, 4);
}
function quantizePolygon(input) {
return input.map(quantizeRing);
}
function quantizeGeometry(o) {
if (o != null && quantizeGeometryType.hasOwnProperty(o.type)) quantizeGeometryType[o.type](o);
}
var quantizeGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(quantizeGeometry); },
Point: function(o) { o.coordinates = quantizePoint(o.coordinates); },
MultiPoint: function(o) { o.coordinates = o.coordinates.map(quantizePoint); },
LineString: function(o) { o.arcs = quantizeLine(o.arcs); },
MultiLineString: function(o) { o.arcs = o.arcs.map(quantizeLine); },
Polygon: function(o) { o.arcs = quantizePolygon(o.arcs); },
MultiPolygon: function(o) { o.arcs = o.arcs.map(quantizePolygon); }
};
for (var key in objects) {
quantizeGeometry(objects[key]);
}
return {
scale: [1 / kx, 1 / ky],
translate: [x0, y0]
};
};
// Constructs the TopoJSON Topology for the specified hash of features.
// Each object in the specified hash must be a GeoJSON object,
// meaning FeatureCollection, a Feature or a geometry object.
var topology = function(objects, quantization) {
var bbox = bounds(objects = geometry(objects)),
transform = quantization > 0 && bbox && prequantize(objects, bbox, quantization),
topology = dedup(cut(extract(objects))),
coordinates = topology.coordinates,
indexByArc = hashmap(topology.arcs.length * 1.4, hashArc, equalArc);
objects = topology.objects; // for garbage collection
topology.bbox = bbox;
topology.arcs = topology.arcs.map(function(arc, i) {
indexByArc.set(arc, i);
return coordinates.slice(arc[0], arc[1] + 1);
});
delete topology.coordinates;
coordinates = null;
function indexGeometry(geometry$$1) {
if (geometry$$1 && indexGeometryType.hasOwnProperty(geometry$$1.type)) indexGeometryType[geometry$$1.type](geometry$$1);
}
var indexGeometryType = {
GeometryCollection: function(o) { o.geometries.forEach(indexGeometry); },
LineString: function(o) { o.arcs = indexArcs(o.arcs); },
MultiLineString: function(o) { o.arcs = o.arcs.map(indexArcs); },
Polygon: function(o) { o.arcs = o.arcs.map(indexArcs); },
MultiPolygon: function(o) { o.arcs = o.arcs.map(indexMultiArcs); }
};
function indexArcs(arc) {
var indexes = [];
do {
var index = indexByArc.get(arc);
indexes.push(arc[0] < arc[1] ? index : ~index);
} while (arc = arc.next);
return indexes;
}
function indexMultiArcs(arcs) {
return arcs.map(indexArcs);
}
for (var key in objects) {
indexGeometry(objects[key]);
}
if (transform) {
topology.transform = transform;
topology.arcs = delta(topology.arcs);
}
return topology;
};
function hashArc(arc) {
var i = arc[0], j = arc[1], t;
if (j < i) t = i, i = j, j = t;
return i + 31 * j;
}
function equalArc(arcA, arcB) {
var ia = arcA[0], ja = arcA[1],
ib = arcB[0], jb = arcB[1], t;
if (ja < ia) t = ia, ia = ja, ja = t;
if (jb < ib) t = ib, ib = jb, jb = t;
return ia === ib && ja === jb;
}
var prune = function(topology) {
var oldObjects = topology.objects,
newObjects = {},
oldArcs = topology.arcs,
oldArcsLength = oldArcs.length,
oldIndex = -1,
newIndexByOldIndex = new Array(oldArcsLength),
newArcsLength = 0,
newArcs,
newIndex = -1,
key;
function scanGeometry(input) {
switch (input.type) {
case "GeometryCollection": input.geometries.forEach(scanGeometry); break;
case "LineString": scanArcs(input.arcs); break;
case "MultiLineString": input.arcs.forEach(scanArcs); break;
case "Polygon": input.arcs.forEach(scanArcs); break;
case "MultiPolygon": input.arcs.forEach(scanMultiArcs); break;
}
}
function scanArc(index) {
if (index < 0) index = ~index;
if (!newIndexByOldIndex[index]) newIndexByOldIndex[index] = 1, ++newArcsLength;
}
function scanArcs(arcs) {
arcs.forEach(scanArc);
}
function scanMultiArcs(arcs) {
arcs.forEach(scanArcs);
}
function reindexGeometry(input) {
var output;
switch (input.type) {
case "GeometryCollection": output = {type: "GeometryCollection", geometries: input.geometries.map(reindexGeometry)}; break;
case "LineString": output = {type: "LineString", arcs: reindexArcs(input.arcs)}; break;
case "MultiLineString": output = {type: "MultiLineString", arcs: input.arcs.map(reindexArcs)}; break;
case "Polygon": output = {type: "Polygon", arcs: input.arcs.map(reindexArcs)}; break;
case "MultiPolygon": output = {type: "MultiPolygon", arcs: input.arcs.map(reindexMultiArcs)}; break;
default: return input;
}
if (input.id != null) output.id = input.id;
if (input.bbox != null) output.bbox = input.bbox;
if (input.properties != null) output.properties = input.properties;
return output;
}
function reindexArc(oldIndex) {
return oldIndex < 0 ? ~newIndexByOldIndex[~oldIndex] : newIndexByOldIndex[oldIndex];
}
function reindexArcs(arcs) {
return arcs.map(reindexArc);
}
function reindexMultiArcs(arcs) {
return arcs.map(reindexArcs);
}
for (key in oldObjects) {
scanGeometry(oldObjects[key]);
}
newArcs = new Array(newArcsLength);
while (++oldIndex < oldArcsLength) {
if (newIndexByOldIndex[oldIndex]) {
newIndexByOldIndex[oldIndex] = ++newIndex;
newArcs[newIndex] = oldArcs[oldIndex];
}
}
for (key in oldObjects) {
newObjects[key] = reindexGeometry(oldObjects[key]);
}
return {
type: "Topology",
bbox: topology.bbox,
transform: topology.transform,
objects: newObjects,
arcs: newArcs
};
};
var filter = function(topology, filter) {
var oldObjects = topology.objects,
newObjects = {},
key;
if (filter == null) filter = filterTrue;
function filterGeometry(input) {
var output, arcs;
switch (input.type) {
case "Polygon": {
arcs = filterRings(input.arcs);
output = arcs ? {type: "Polygon", arcs: arcs} : {type: null};
break;
}
case "MultiPolygon": {
arcs = input.arcs.map(filterRings).filter(filterIdentity);
output = arcs.length ? {type: "MultiPolygon", arcs: arcs} : {type: null};
break;
}
case "GeometryCollection": {
arcs = input.geometries.map(filterGeometry).filter(filterNotNull);
output = arcs.length ? {type: "GeometryCollection", geometries: arcs} : {type: null};
break;
}
default: return input;
}
if (input.id != null) output.id = input.id;
if (input.bbox != null) output.bbox = input.bbox;
if (input.properties != null) output.properties = input.properties;
return output;
}
function filterRings(arcs) {
return arcs.length && filterExteriorRing(arcs[0]) // if the exterior is small, ignore any holes
? [arcs[0]].concat(arcs.slice(1).filter(filterInteriorRing))
: null;
}
function filterExteriorRing(ring) {
return filter(ring, false);
}
function filterInteriorRing(ring) {
return filter(ring, true);
}
for (key in oldObjects) {
newObjects[key] = filterGeometry(oldObjects[key]);
}
return prune({
type: "Topology",
bbox: topology.bbox,
transform: topology.transform,
objects: newObjects,
arcs: topology.arcs
});
};
function filterTrue() {
return true;
}
function filterIdentity(x) {
return x;
}
function filterNotNull(geometry) {
return geometry.type != null;
}
var filterAttached = function(topology) {
var ownerByArc = new Array(topology.arcs.length), // arc index -> index of unique associated ring, or -1 if used by multiple rings
ownerIndex = 0,
key;
function testGeometry(o) {
switch (o.type) {
case "GeometryCollection": o.geometries.forEach(testGeometry); break;
case "Polygon": testArcs(o.arcs); break;
case "MultiPolygon": o.arcs.forEach(testArcs); break;
}
}
function testArcs(arcs) {
for (var i = 0, n = arcs.length; i < n; ++i, ++ownerIndex) {
for (var ring = arcs[i], j = 0, m = ring.length; j < m; ++j) {
var arc = ring[j];
if (arc < 0) arc = ~arc;
var owner = ownerByArc[arc];
if (owner == null) ownerByArc[arc] = ownerIndex;
else if (owner !== ownerIndex) ownerByArc[arc] = -1;
}
}
}
for (key in topology.objects) {
testGeometry(topology.objects[key]);
}
return function(ring) {
for (var j = 0, m = ring.length, arc; j < m; ++j) {
if (ownerByArc[(arc = ring[j]) < 0 ? ~arc : arc] === -1) {
return true;
}
}
return false;
};
};
function planarTriangleArea(triangle) {
var a = triangle[0], b = triangle[1], c = triangle[2];
return Math.abs((a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1])) / 2;
}
function planarRingArea$1(ring) {
var i = -1, n = ring.length, a, b = ring[n - 1], area = 0;
while (++i < n) a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0];
return Math.abs(area) / 2;
}
var filterWeight = function(topology, minWeight, weight) {
minWeight = minWeight == null ? Number.MIN_VALUE : +minWeight;
if (weight == null) weight = planarRingArea$1;
return function(ring, interior) {
return weight(feature(topology, {type: "Polygon", arcs: [ring]}).geometry.coordinates[0], interior) >= minWeight;
};
};
var filterAttachedWeight = function(topology, minWeight, weight) {
var a = filterAttached(topology),
w = filterWeight(topology, minWeight, weight);
return function(ring, interior) {
return a(ring, interior) || w(ring, interior);
};
};
function compare(a, b) {
return a[1][2] - b[1][2];
}
var newHeap = function() {
var heap = {},
array = [],
size = 0;
heap.push = function(object) {
up(array[object._ = size] = object, size++);
return size;
};
heap.pop = function() {
if (size <= 0) return;
var removed = array[0], object;
if (--size > 0) object = array[size], down(array[object._ = 0] = object, 0);
return removed;
};
heap.remove = function(removed) {
var i = removed._, object;
if (array[i] !== removed) return; // invalid request
if (i !== --size) object = array[size], (compare(object, removed) < 0 ? up : down)(array[object._ = i] = object, i);
return i;
};
function up(object, i) {
while (i > 0) {
var j = ((i + 1) >> 1) - 1,
parent = array[j];
if (compare(object, parent) >= 0) break;
array[parent._ = i] = parent;
array[object._ = i = j] = object;
}
}
function down(object, i) {
while (true) {
var r = (i + 1) << 1,
l = r - 1,
j = i,
child = array[j];
if (l < size && compare(array[l], child) < 0) child = array[j = l];
if (r < size && compare(array[r], child) < 0) child = array[j = r];
if (j === i) break;
array[child._ = i] = child;
array[object._ = i = j] = object;
}
}
return heap;
};
function copy(point) {
return [point[0], point[1], 0];
}
var presimplify = function(topology, weight) {
var point = topology.transform ? transform(topology.transform) : copy,
heap = newHeap();
if (weight == null) weight = planarTriangleArea;
var arcs = topology.arcs.map(function(arc) {
var triangles = [],
maxWeight = 0,
triangle,
i,
n;
arc = arc.map(point);
for (i = 1, n = arc.length - 1; i < n; ++i) {
triangle = [arc[i - 1], arc[i], arc[i + 1]];
triangle[1][2] = weight(triangle);
triangles.push(triangle);
heap.push(triangle);
}
// Always keep the arc endpoints!
arc[0][2] = arc[n][2] = Infinity;
for (i = 0, n = triangles.length; i < n; ++i) {
triangle = triangles[i];
triangle.previous = triangles[i - 1];
triangle.next = triangles[i + 1];
}
while (triangle = heap.pop()) {
var previous = triangle.previous,
next = triangle.next;
// If the weight of the current point is less than that of the previous
// point to be eliminated, use the latter’s weight instead. This ensures
// that the current point cannot be eliminated without eliminating
// previously- eliminated points.
if (triangle[1][2] < maxWeight) triangle[1][2] = maxWeight;
else maxWeight = triangle[1][2];
if (previous) {
previous.next = next;
previous[2] = triangle[2];
update(previous);
}
if (next) {
next.previous = previous;
next[0] = triangle[0];
update(next);
}
}
return arc;
});
function update(triangle) {
heap.remove(triangle);
triangle[1][2] = weight(triangle);
heap.push(triangle);
}
return {
type: "Topology",
bbox: topology.bbox,
objects: topology.objects,
arcs: arcs
};
};
var quantile = function(topology, p) {
var array = [];
topology.arcs.forEach(function(arc) {
arc.forEach(function(point) {
if (isFinite(point[2])) { // Ignore endpoints, whose weight is Infinity.
array.push(point[2]);
}
});
});
return array.length && quantile$1(array.sort(descending), p);
};
function quantile$1(array, p) {
if (!(n = array.length)) return;
if ((p = +p) <= 0 || n < 2) return array[0];
if (p >= 1) return array[n - 1];
var n,
h = (n - 1) * p,
i = Math.floor(h),
a = array[i],
b = array[i + 1];
return a + (b - a) * (h - i);
}
function descending(a, b) {
return b - a;
}
var simplify = function(topology, minWeight) {
minWeight = minWeight == null ? Number.MIN_VALUE : +minWeight;
// Remove points whose weight is less than the minimum weight.
var arcs = topology.arcs.map(function(input) {
var i = -1,
j = 0,
n = input.length,
output = new Array(n), // pessimistic
point;
while (++i < n) {
if ((point = input[i])[2] >= minWeight) {
output[j++] = [point[0], point[1]];
}
}
output.length = j;
return output;
});
return {
type: "Topology",
transform: topology.transform,
bbox: topology.bbox,
objects: topology.objects,
arcs: arcs
};
};
var pi = Math.PI;
var tau = 2 * pi;
var quarterPi = pi / 4;
var radians = pi / 180;
var abs = Math.abs;
var atan2 = Math.atan2;
var cos = Math.cos;
var sin = Math.sin;
function halfArea(ring, closed) {
var i = 0,
n = ring.length,
sum = 0,
point = ring[closed ? i++ : n - 1],
lambda0, lambda1 = point[0] * radians,
phi1 = (point[1] * radians) / 2 + quarterPi,
cosPhi0, cosPhi1 = cos(phi1),
sinPhi0, sinPhi1 = sin(phi1);
for (; i < n; ++i) {
point = ring[i];
lambda0 = lambda1, lambda1 = point[0] * radians;
phi1 = (point[1] * radians) / 2 + quarterPi;
cosPhi0 = cosPhi1, cosPhi1 = cos(phi1);
sinPhi0 = sinPhi1, sinPhi1 = sin(phi1);
// Spherical excess E for a spherical triangle with vertices: south pole,
// previous point, current point. Uses a formula derived from Cagnoli’s
// theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2).
// See https://github.com/d3/d3-geo/blob/master/README.md#geoArea
var dLambda = lambda1 - lambda0,
sdLambda = dLambda >= 0 ? 1 : -1,
adLambda = sdLambda * dLambda,
k = sinPhi0 * sinPhi1,
u = cosPhi0 * cosPhi1 + k * cos(adLambda),
v = k * sdLambda * sin(adLambda);
sum += atan2(v, u);
}
return sum;
}
function sphericalRingArea(ring, interior) {
var sum = halfArea(ring, true);
if (interior) sum *= -1;
return (sum < 0 ? tau + sum : sum) * 2;
}
function sphericalTriangleArea(t) {
return abs(halfArea(t, false)) * 2;
}
exports.bbox = bbox;
exports.feature = feature;
exports.mesh = mesh;
exports.meshArcs = meshArcs;
exports.merge = merge;
exports.mergeArcs = mergeArcs;
exports.neighbors = neighbors;
exports.quantize = quantize;
exports.transform = transform;
exports.untransform = untransform;
exports.topology = topology;
exports.filter = filter;
exports.filterAttached = filterAttached;
exports.filterAttachedWeight = filterAttachedWeight;
exports.filterWeight = filterWeight;
exports.planarRingArea = planarRingArea$1;
exports.planarTriangleArea = planarTriangleArea;
exports.presimplify = presimplify;
exports.quantile = quantile;
exports.simplify = simplify;
exports.sphericalRingArea = sphericalRingArea;
exports.sphericalTriangleArea = sphericalTriangleArea;
Object.defineProperty(exports, '__esModule', { value: true });
})));
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment