• Stars
    star
    160
  • Rank 234,703 (Top 5 %)
  • Language
    HTML
  • Created about 10 years ago
  • Updated about 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A server-less boundary service. Find what geographic feature (e.g. an election district) a given point lies in.

Wherewolf

Build Status

Wherewolf

Wherewolf is a server-less boundary service. Given a set of geographic features and a point, it will tell you which feature that point lies in.

var teenWolf = Wherewolf();
teenWolf.add("Flood Zone",floodZones);

var result = teenWolf.find({lat: 40.7, lng: -74.1});
console.log(result["Flood Zone"]);
//{zone: "A"}

We've used earlier versions of this approach for things like the 2014 Election Guide, where you enter your address to see which state assembly, state senate, and congressional district you're in, and SchoolBook, where we locate your school district based on your address.

Wherewolf uses latitude/longitude points, so if you're starting from an address, you need to geocode it first. See the Examples section below for how to geocode addresses and feed them to a Wherewolf.

A more full-featured alternative to Wherewolf is django-boundaryservice. The upside of Wherewolf is that you don't have to configure, administer, or pay for a database or a server. For some details on performance considerations, see the Performance and Fine Print sections at the bottom.

Use/Requirements

You need a GeoJSON or TopoJSON version of your boundaries.

If you're summoning a Wherewolf in a browser, include wherewolf.js as a normal script:

<script src="wherewolf.js"></script>
<script>
  var teenWolf = Wherewolf();
</script>

If you're using GeoJSON boundaries, there are no dependencies. If you're using TopoJSON, you must include the TopoJSON client library first:

<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="wherewolf.js"></script>

Wherewolf is available as a Node module, installed via npm.

npm install wherewolf

Wherewolf can also be installed with Bower.

bower install --save wherewolf

API

To summon a Wherewolf, call the Wherewolf constructor:

//The full moon is out
var teenWolf = Wherewolf();

Wherewolf.add(layerName,features[,objectName])

//Add one layer
teenWolf.add("US State",states)

//Add multiple layers
teenWolf.add("US State",states)
        .add("County",counties);

Adds a new layer of GeoJSON or TopoJSON features with the name layerName to a Wherewolf.

features can be any one of:

  • A GeoJSON FeatureCollection
  • An array of GeoJSON features
  • A single GeoJSON feature
  • A TopoJSON Topology

If features is a TopoJSON topology with multiple objects (e.g. you have a TopoJSON file with counties and states in the same file), you must specify which objectName to add.

teenWolf.add("US County",statesAndCounties,"counties");

Returns: the updated Wherewolf.

Wherewolf.addAll(topology)

teenWolf.addAll(statesAndCounties);

Adds each TopoJSON object in topology as a layer to the Wherewolf. Each object's name is used as its layer name. The above example is equivalent to:

for (var name in statesAndCounties.objects) {
  teenWolf.add(name,statesAndCounties,name);
}

Returns: the updated Wherewolf.

Wherewolf.find(point[,options])

var results = teenWolf.find([-75.15,30.2]);

Returns an object with the properties of the matching feature from each Wherewolf layer that the point is found in. For any layer where the point has no match, the result will be null.

point can be either an array of [lng,lat] (LONGITUDE FIRST), or a literal:

var results = teenWolf.find({ lat: 30.2, lng: -75.15 });

options is an object with two possible options: layer and wholeFeature.

Setting layer to a layer name will get the result for that layer only (default: all layers).

var results = teenWolf.find(point, { layer: "US State" });

Setting wholeFeature will return the whole matching feature as GeoJSON, rather than just its properties.

var results = teenWolf.find(point, { wholeFeature: true });

To see the difference between various options, check out the Wherewolf options playground.

Returns: the Wherewolf's search results.

Wherewolf.get(layerName)

var states = teenWolf.get("US State");

Returns an array of GeoJSON features saved as layer layerName. If layerName does not exist, returns null. The above example would return:

[
  {
    type: "Feature",
    properties: { name: "California" },
    geometry: ...
  },
  {
    type: "Feature",
    properties: { name: "Arizona" },
    geometry: ...
  },
  ...
]

Returns: an array of GeoJSON features, or null.

Wherewolf.remove(layerName)

teenWolf.remove("County");

Remove the layer with name layerName if it exists.

Returns: the updated Wherewolf object.

Wherewolf.layerNames()

var names = teenWolf.layerNames();
//["US State","County"]

Get the names of all existing layers in a Wherewolf.

Returns: an array of current layer names.

Examples

Performance

As a stress test of performance, we tried adding all 3141 counties in the United States as a Wherewolf layer and then searching it for 5000 random nearby points. In most instances, finding the containing feature for a point took less than a tenth of a millisecond. In the worst case, it took about 8 milliseconds.

  • Chrome 38 (OS X, Macbook Pro): 53ms to summon, 0.08ms to search each point
  • Safari 7.0.2 (OS X, Macbook Pro): 11ms to summon, 0.07ms to search each point
  • Firefox 33 (OS X, Macbook Pro): 23ms to summon, 0.07ms to search each point
  • Chrome 34 (Android 4.4.4, Nexus 5): 53ms to summon, 0.72ms to search each point
  • IE9 (Windows 7): 32ms to summon, 0.62ms to search each point
  • IE10 (Windows 7): 20ms to summon, 0.54ms to search each point
  • Firefox 29 (Android 2.3.4): 267ms to summon, 8.2ms to search each point
  • Mobile Safari (iOS 7, iPhone 5C): 48ms to summon, 1.1ms to search each point
  • Chrome 38 (iOS 7, iPad Mini): 53ms to summon, 1.74ms to search each point

The main performance limitation when using Wherewolf is the size of the GeoJSON or TopoJSON files you have to load in before you can search. But you can get these files pretty small by a) converting to TopoJSON, b) removing extraneous attribute info, and c) gzipping. Even the file of all the counties in the US is only 78k as gzipped TopoJSON, which will download in about 1.8 seconds on a 3G connection, or 73 milliseconds on a 30mbps Wifi connection.

One thing to consider is that loading a data file will be asynchronous, so it won't block the rest of your page from loading. In most cases, a user is likely to spend some time on the page before doing anything that relies on Wherewolf, so that gives you extra time. The general pattern might look something like this:

var pending,
    wolf;

//Load the data asynchronously
$.getJSON("school-districts.geojson",function(data){

    //Data is ready, summon the wherewolf
    wolf = Wherewolf();
    wolf.add("School District",data);

    //If there's a pending search, do the lookup
    if (pending) {
        pending = false;
        lookup(pending);
    }

});

//When they try to search, if the data
//has loaded, do the lookup.  Otherwise,
//save the submission as pending for when
//the data finishes
$("#search-button").on("click",function(){
    //add a loading state
    $("div#search-form").addClass("loading");

    //If the data's ready, do the lookup immediately;
    if (wolf) {
        lookup($("input#location").val());
    //Otherwise set it aside as pending
    } else {
        pending = $("input#location").val();
    }
});

function lookup(location) {
    //Lookup the location
    //do some stuff with the results

    //Remove the loading state
    $("div#search-form").removeClass("loading");
}

Getting fancy

If you have concerns about initial load time due to file size, a fancy approach would to be divide the features into subsets and only initially load a GeoJSON file with the bounding box for each subset. For example, you could have:

  • quadrants.topojson
  • northwest-us.topojson
  • southwest-us.topojson
  • northeast-us.topojson
  • southeast-us.topojson

Then you could do something like:

ww.add("quadrant",quadrants);

//When you need to search...
var quadrant = ww.find([lng,lat],{layer:"quadrant"});

//If they are in one of the four rectangles,
//Load that file and search it
//You're only loading/search 25% of the counties
if (quadrant.name) {
  $.getJSON(quadrant.name+".topojson",function(counties){
    ww.add("counties",counties);
    var theCountyTheyAreIn = ww.find([lng,lat],{layer:"counties"});
  });
}

In this way, you load very little data up front, but the downside is you introduce some extra delay at the time of the search.

Fine print

  • This will probably not work for a feature that crosses the North or South Pole.
  • This may not work for certain special cases of a point that lies right on the antimeridian being checked against a feature that crosses the antimeridian. It's unclear whether any scenario on the actual earth can cause this problem. The Aleutian Islands work fine.
  • Wherewolf will only match against GeoJSON Polygons,MultiPolygons,and Points. It will not work with a feature that's a LineString.
  • If your geographic data file is invalid, you may get unpredictable results. Wherewolf assumes that your polygons do not self-intersect or overlap. If a point matches more than one feature in one layer, Wherewolf will return the first match it finds.
  • Your results will only be as accurate as your data is precise. If the boundaries you're using are highly simplified, you may get inaccurate results for points near a border.

Credits/License

By Noah Veltman and Jenny Ye

Special thanks to:

Available under the MIT license.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

More Repositories

1

flubber

Tools for smoother shape animations.
JavaScript
6,378
star
2

clmystery

A command-line murder mystery
5,317
star
3

principles

Things to keep in mind when making stuff for the web
1,265
star
4

learninglunches

Materials for a series of learning lunches on news development topics.
237
star
5

gifs

Testing various ways of generating gifs and videos off data-driven JS animations.
HTML
179
star
6

csvgeocode

Node module for bulk geocoding addresses in a CSV.
JavaScript
163
star
7

mapstarter

A tool for generating starter SVG maps from a geographic data source
JavaScript
129
star
8

ca-license-plates

Vanity license plate applications from the California DMV.
122
star
9

pancakejs

A mini-library for easily flattening SVG and Canvas elements into images on the fly.
JavaScript
86
star
10

d3-stateplane

D3-friendly State Plane projections
86
star
11

openvis

Links and bibliography for OpenVisConf 2017
84
star
12

fourscore

An open-source version of the WNYC sentiment tracker.
JavaScript
46
star
13

nflplays

Cleaned-up NFL play-by-play data from 2002-2012
42
star
14

stakeout

For watching a set of URLs and notifying someone when something has changed.
JavaScript
31
star
15

xyz-affair

Generate list of x/y/z Spherical Mercator tiles based on a bounding box and a zoom level.
JavaScript
31
star
16

endorsements

Data on newspaper presidential endorsements
28
star
17

markdowneyjr

A hacky Markdown-to-JSON parser for easier copy editing.
JavaScript
25
star
18

loopify

Seamless looping with the WebAudioAPI.
JavaScript
22
star
19

maps-nicar14

Notes for map session with Tom MacWright (@tmcw) at NICAR14.
21
star
20

d3-unconf

HTML
17
star
21

presidential-nouns

Nouns of assemblage for US presidents.
13
star
22

flipbookjs

For automatically flip-booking progress while developing something for the web.
JavaScript
13
star
23

snd3

D3.js resources for SND/NYC 2018
HTML
13
star
24

mahalanobis

Calculate Mahalanobis distances for multivariate data.
JavaScript
13
star
25

ire2014

A list of resources for getting started with interactive news projects.
12
star
26

notsimple

Chrome extension that puts quotes around any instance of the words 'simple,' 'simply,' 'easy,' and 'easily' in GitHub and RTD docs.
JavaScript
11
star
27

gobblefunk

Rename your JS variables with silly words derived from Dr. Seuss, Roald Dahl, and Lewis Carroll.
JavaScript
9
star
28

hashnav

Simple JS hash-based navigation library
JavaScript
9
star
29

oh-snap

Snap points to nearest point in a different set.
JavaScript
7
star
30

hhbaworkshop

Mapping History - A Leaflet.js workshop for the Hacks/Hackers Buenos Aires Media Party
CSS
6
star
31

headless-gif

Saving web worker'd gif via PhantomJS.
JavaScript
6
star
32

dotmapper

Auto-generate a dotmap from a GeoJSON file
JavaScript
5
star
33

node-geosupport

Node.js wrapper for fast geocoding with NYC's Geosupport system
JavaScript
5
star
34

streets

A bunch of convoluted processing for turning OSM data into Leaflet-mappable street objects with names.
Python
5
star
35

fresh-start

A full bootstrap script for a new installation of Ubuntu Desktop
Shell
5
star
36

presidential-election-results

1976-2020 presidential election popular vote totals by state and party
4
star
37

chopped-and-viewed

Node module for chopping fixed-width text files.
JavaScript
4
star
38

congressional-acronyms

Raw data on congressional acronyms, 1972-2013
3
star
39

insecurity

Analyze HTML, CSS, JS, etc. files for insecure URLs.
JavaScript
3
star
40

nicar15-scrapeoff

Gathering notes/suggestions for a Scrape-off at NICAR 15.
2
star
41

knickout

For simulating the rest of the Knicks season.
JavaScript
2
star
42

point-on-line

Test whether a point is on a line or LineString
JavaScript
2
star
43

postal-abbreviations

A no-fuss US postal abbreviations module.
JavaScript
2
star
44

BriefMemorableSlug

Generate random adjective-adjective-animal slugs
JavaScript
1
star
45

state-population-by-age

State population by age, 1960-2040
1
star
46

dst

Collecting data to visualize effects of DST
Python
1
star
47

lazy-vector-tiles

A lazy vector tile system. Not robust or high-performance, but rather straightforward.
JavaScript
1
star
48

bracket

CSS
1
star
49

rosetta

TK
1
star
50

euler

Solving Project Euler problems in JS, Python, and maybe Ruby
JavaScript
1
star
51

gulp-insecurity

Gulp plugin for detecting insecure URLs that could cause mixed content errors.
JavaScript
1
star
52

statelympics

Python
1
star
53

webpack-babel-bug-repro

JavaScript
1
star
54

fuel-prices

Misc. fuel price data
JavaScript
1
star
55

recs

Travel recommendations in Markdown
CSS
1
star
56

ticker-tape

A list of ticker tape parades in New York City
1
star
57

jsv

1
star
58

emojify

Replace PHP variable names with emojis.
PHP
1
star
59

supreme-court-transcripts

Attempting to parse supreme court oral argument transcripts.
JavaScript
1
star
60

split-multipart-features

A script for selectively splitting up multipart features in QGIS.
Python
1
star
61

nyc-css

NYC in CSS (Chrome only)
HTML
1
star