block by arnicas d210166c6d302da09397

Dots for rats in 2016 on a leaflet map, with generated bios and pics from flickr

Full Screen

index.html

<!DOCTYPE html>

<meta charset="utf-8">

<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
<link href='https://fonts.googleapis.com/css?family=Merriweather' rel='stylesheet' type='text/css'>
<style>

body {
  font-family: Merriweather, sans-serif;
  background-color: linen;
  font-size: 10pt;
  padding: 20px;
}

p {
  width: 700px;
}

#map {
  height: 700px;
  width: 700px;
}

.bio {
  font-style: italic;
  font-color: blue;
  margin-left: 10px;
}

.facts {
  margin-left: 5px;
  font-style: bolder;
  font-color: darkgray;
}

</style>
<body>

<h2>Every Rat in NYC (That People Complained About in 2016)</h2>
<p>Source: 2016 Rats <a href="https://data.cityofnewyork.us/Social-Services/Rat-Sightings/3q43-55fe">NYC Open Data on 311 calls for rat sightings</a>, via Jeremy Singer-Vine's newsletter. Click on a rat dot to learn about them.  Their pics are from nearby, but may not have been taken by the rat personally. (Created with @GalaxyKate's <a href="https://github.com/galaxykate/tracery">Tracery.js</a> and the flickr API. For <a href="https://twitter.com/savasavasava">@savasavasava</a> and <a href="https://twitter.com/meetar">@meetar</a> and <a href="https://twitter.com/CaseyG">@CaseyG</a>.)</p>

<div id="map"></div>

<script src="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.2.min.js" integrity="sha256-lZFHibXzMHo3GGeehn1hudTAP3Sc0uKXBXAzHX1sjtk=" crossorigin="anonymous"></script>
<script src="tracery.js"></script>
<script>


var dateFormat = d3.time.format("%_m/%d/%y");
var searchDateFormat = d3.time.format("%Y-%m-%d");

var story;

// Try different tile servers:
// '//korona.geog.uni-heidelberg.de/tiles/roadsg/x={x}&y={y}&z={z}'
// "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"

var map = new L.Map("map", {center: [40.74, -73.9], zoom: 12})
    .addLayer(L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
	attribution: 'Tiles &copy; Esri &mdash; Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
})
	     );

// we use queue because we have 2 data files to load.
queue()
  .defer(d3.csv, "Rat_SightingsNY_extract.csv", filterData) // process
  .await(loaded);

function loaded(error, rats) {
  if (error) throw error;

  var ratMarkers = [];

  rats.forEach(function(rat) {
    var circle = L.circleMarker([+rat.Latitude, +rat.Longitude], {
      stroke: false,
      color: 'red',
      fillColor: '#f03',
      fillOpacity: 0.3,
      className: 'rat'
      }).setRadius(2).addTo(map);
    circle.bindPopup("Loading...");
    circle.on('click', function(e) {
      var string = "<div class='facts'>" + dateFormat(rat.date) + "<br>Rat at " + rat["Location Type"] + ",<br>"
        + rat["Incident Address"] + "<br>" + rat.Borough + "</div><br><div class='bio'>";
      string += getStory(rat);
      e.target.bindPopup(string);
      var url= getUrl(rat);
      $.get(url).done(function(data) {
          string += getPic(data);
          string += "</div>";
          e.target.bindPopup(string);
        });
    });
    ratMarkers.push(circle);
    });

  // leaflet maps come with a lot of useful functions:
  map.on('zoomend', function() {
    var currentZoom = map.getZoom();
    var bounds = map.getBounds();
    //console.log("current zoom", currentZoom);
    ratMarkers.forEach(function(r) {
      if (bounds.contains(r.getLatLng())) { // try to make it more efficient
        if (currentZoom > 12) {
          r.setRadius(4);
        } else {
          r.setRadius(2);
        }
      }
    });
  });

} // end loaded;


function filterData(d) {
  // filter out the data before 2015 -- too much otherwise
  var dateFormat = d3.time.format("%_m/%d/%y %H:%M");
  d.date = dateFormat.parse(d["Created Date"]);
  d.year = d.date.getFullYear();
  if (d.year == 2016) {
    return d;
  }
}


function getRandom(list) {

  function getRandomIntInclusive(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
  }

  return list[getRandomIntInclusive(0, list.length-1)];
}

function getUrl(rat) {

  var baseUrl = "https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=e08843a8ba2ec0444013a39cf4082568&per_page=25&format=json&nojsoncallback=1&safe_search=1";

  var date = "&max_taken_date=" + searchDateFormat(rat.date);
  var search = "&lat=" + rat.Latitude.slice(0,5) + "&lon=" + rat.Longitude.slice(0,5) + date;

  if (rat.Borough == "BROOKLYN") {
    search += "&tags=Brooklyn,cheese,food,pub,rat,mouse,bar,home,cafe,coffee,beer,wine";
  }

  if (rat.Borough == "MANHATTAN") {
    search += "&tags=nyc,NYC,NewYork,dinner,rat,mouse,food,street,subway,train,bus,cars,trash,restaurant";
  }

  //console.log("search string", baseUrl + search);
  return baseUrl + search;
}


function getPic(result) {
  // result is from an API call to flickr
  var photo = getRandom(result.photos.photo);
  var farmId = photo.farm;
  var serverId = photo.server;
  var id = photo.id;
  var secret = photo.secret;

  // _t is a size attribute, see https://www.flickr.com/services/api/misc.urls.html
  var url = "https://farm" + farmId + ".staticflickr.com/" + serverId + "/" + id + "_" + secret + "_t.jpg";

  return "<br>Last snap by our rat:<br><div class='img'><img src='" + url + "'/></div>";

  //https://farm{farm-id}.staticflickr.com/{server-id}/{id}_{secret}_[mstzb].jpg

}

function createStoryRules(rat) {

  // So many ways this could be improved, based on borough!  So little time. :(

  if (rat.Borough == "BROOKLYN") {
    var food = ["artisinal cheeses", "raw vegan food", "probiotics", "pourover coffee with fairtrade beans from a bur grinder", "gluten-free mini cupcakes", "craft brews", "herbal cocktails", "imported Spanish chorizo", "fairtrade chocolate", "home-schooled children", "labradoodle pups"];
    var doing = ["going to farmers markets", "lurking underfoot in brew pubs", "going for a long coffee with friends", "bathing in craft beers", "painting rat portraits on orange rinds", "posting on Medium", "making whisker lace for her Etsy shop", "shopping for cheeses online", "nesting in hipster beards", "pooping in hipster hats"];

  } else if (rat.Borough == "MANHATTAN") {

    var food = ["hot knishes", "candy bar wrappers", "mini cupcakes", "pizza slices", "lobster", "noodles", "dim sum", "fries", "McDonalds", "fresh bagels", "sushi", "Chinese food", "tacos"];
    var doing = ["chasing subway trains", "lurking under platforms", "riding the third rail", "long scampers in Central Park", "visiting museums at night", "hiding in trash cans", "eating garbage", "preening on fire escapes", "getting #heroTheir# fur and nails done", "going for high tea", "shopping on the Upper East", "browsing in bookstores", "eating books and magazines", "nesting in rare books", "sleeping in the silver drawer"];
  } else {

    var food = ["Gouda", "Swiss Cheese", "ham and rye", "chocolate cake", "pie", "hotdogs", "bread crusts", "cold pizza", "old pizza", "potato chip crumbs", "cat food", "dog food", "nachos", "bagels", "donuts", "cronuts", "Italian food", "Chinese food", "French food", "kimchee", "cats", "roaches", "yogurt", "a good cupcake", "cookies"];
    var doing = ["watching TV", "cleaning #heroTheir# fur", "teaching baby rats", "hanging in dark holes", "sharpening #heroTheir# teeth and claws", "tormenting cats", "teasing dogs", "sleeping", "long scampers on beaches", "running in parks", "meeting new single rats", "hanging with mice and other rodents", "swimming in sewage", "sampling people food", "playing chicken with subway trains", "playing in traffic", "sneaking up on people"];
  }

  var rules = {
    "street": rat["Street Name"].split(" ")[0].toLowerCase() || "Ratface",
    "mcRat": "Mc#street.capitalize#Face",
    "name": ["Ratty #mcRat#", "'#food.capitalize#' Rat"],
    "age": d3.range(0,30).map(function(d) {return d.toString();}),
    "cheers": ["FTW!", "YOLO, right?", "Yay!", "Whatevs.", "Chillin'.", "GTD.", "No regrets!", "'S all good.", "It's a rat's life.", "Vermin FTW!", "Sometimes #heroThey# is like, 'wtf', though."],
    "andDoing": doing,
    "food": food,
    "likes": ["#heroThey.capitalize# loves #food# and #andDoing#.", "#heroThey.capitalize# likes #food# and #andDoing#.", "#food.capitalize# really appeals to #heroThem#.", "#heroThey.capitalize# really enjoys some good #food#, but also #andDoing#."],
    "setPronouns": ["[heroThey:she][heroThem:her][heroTheir:her][heroTheirs:hers]","[heroThey:he][heroThem:him][heroTheir:his][heroTheirs:his]"],
    "Adj": ["a fine", "a passable", "an ambitious", "an exemplary", "a great", "a superior", "a lovely", "an ugly but lovable", "a fun", "a social", "a lazy", "an unpleasant", "a playful", "an annoying"],
    "story": ["#name# is #Adj# rat. #heroThey.capitalize# was #age# weeks old at sighting. #likes# #cheers#"],
    "origin": ["#[#setPronouns#]story#"]
  };
    return tracery.createGrammar(rules);
}

function getStory(rat) {
  story = createStoryRules(rat);
  return story.flatten("#origin#");
}

</script>