Made README more pithy, moved more detailed stuff to docs and linked

This commit is contained in:
noncomputable 2018-08-31 01:19:24 -04:00
parent 4e75f08616
commit 80a1a5f89f

258
README.md
View File

@ -1,262 +1,36 @@
# AgentMaps - Social Simulations on Interactive Maps
AgentMaps is a Javascript library for building and visualizing dynamic social systems on maps (or technically, "geospatial agent-based simulations").
It is based on the [Leaflet](https://leafletjs.com/) interactive mapping [library](https://github.com/Leaflet/Leaflet) and operates as an extension of it.
Given some information about a neighborhood, like a pair of points forming two corners of a rectangle that contains it and [GeoJSON](http://geojson.org/) data representing its streets,
AgentMaps lets you quickly and easily do the following:
AgentMaps is a Javascript library for building and visualizing dynamic social systems on maps.
It is based on the [Leaflet](https://leafletjs.com/) interactive mapping [library](https://github.com/Leaflet/Leaflet).
Given a neighborhood, AgentMaps lets you quickly and easily:
* Generate buildings ("units") along the streets.
* Inspect and navigate between buildings and streets.
* Spawn agents and embed them into the map.
* Give agents rules of behavior.
* Schedule agents to move between units and streets ("places") on the map.
* Track the time and control the state of the simulation.
* Build units along the streets.
* Spawn agents onto the map.
* Schedule them to move them between places on the map.
* Change their appearance and properties.
In short, it's something like a bare-bones SimCity factory.
### Documentation
#### Table of Contents
[Prerequisites](#prerequisites)
Docs for people who want to use AgentMaps are available at <https://noncomputable.github.io/AgentMaps/docs/index.html>.
[Basic Walkthrough](#basic-walkthrough)
For people who want to contribute to AgentMaps or understand its internals, see <https://noncomputable.github.io/AgentMaps/devdocs/index.html>.
[Documentation](#documentation)
[Demos](#demos)
A basic walkthrough for creating an AgentMaps simulation is here: <https://noncomputable.github.io/AgentMaps/docs/tutorial-quickstart.html>.
[Feature Wishlist](#feature-wishlist)
### Demos
[Authors](#authors)
[Simple](https://noncomputable.github.io/AgentMaps/demos/simple/simple.html): Demonstrates all the different ways agents can travel around a map.
[Acknowledgements](#acknowledgements)
# Getting Started
## Prerequisites
First of all, you can find a bundle for AgentMaps here: <https://unpkg.com/agentmaps@2.0.0/site/dist/agentmaps.js>.
Making simulations with AgentMaps will be a lot easier for you if you can:
* Program with Javascript
* Use the [Leaflet](https://leafletjs.com/) mapping library
Leaflet doesn't come bundled with AgentMaps, so you'll have to either include it in your web page with its own \<script\> tag or
install it with [npm](https://www.npmjs.com/package/leaflet) and bundle it yourself.
It might also help to be familiar with [turf.js](http://turfjs.org/), a library that contains lots of tools that make geospatial work (like intersection detection and line slicing) quick and easy.
AgentMaps expects geographic data in the form of [GeoJSON](http://geojson.org/), a format for representing geospatial information,
so it would be useful to take a look at that.
How do you get the GeoJSON data of some neighborhood you're interested in? I use [OpenStreetMap](https://www.openstreetmap.org/) (OSM),
a free, collaborative map of the world! You can get a JSON file by using the "export" tool on the OSM website;
you can also use it to get the coordinates of the two points bounding your neighborhood.
All of the above is pretty important to be able to contribute to AgentMaps or understand its internal implementation as well.
## Basic Walkthrough
Here, we'll walk through building a simple AgentMaps simulation. I suggest looking at the detailed [documentation](#documentation) for
all of the AgentMaps functions and classes used here to get a better understanding of how they work.
If you're not so familiar with Leaflet, I suggest doing the same with the [Leaflet docs](https://leafletjs.com/reference-1.3.2.html).
First, create an HTML document that:
* Loads the Leaflet style and script
* Contains a \<div\> in which to insert the Leaflet map
* Loads the AgentMaps script at the end
```HTML
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="leaflet_style.css">
<script src="leaflet_script.js"></script>
</head>
<body>
<div id="demo_map" style="height:400px"></div>
<script src="agentmaps.js"></script>
</body>
</html>
```
Assume we have the GeoJSON of a neighborhood stored in a variable like this, where the ellipses stand in for a list of map features (like streets):
```javascript
let map_data = {
"type": "FeatureCollection",
"features": [
...
...
...
]
};
```
Have two opposite corners of a rectangle containing the neighborhood of interest stored as an array of the corners' coordinates (i.e. [[lat, lon], [lat, lon]]):
```javascript
let bounding_points = [[43.3071, -88.0158], [43.2884, -87.9759]];
```
Create a Leaflet map in the "demo\_map" \<div\> of our HTML document:
```javascript
let map = L.map("demo_map").fitBounds(bounding_points);
```
The map will be empty, so tile it with OpenStreetMap's map tiles to see what's where:
```javascript
L.tileLayer(
"http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
{
attribution: "Thanks to <a href=\"http://openstreetmap.org\">OpenStreetMap</a> community",
}
).addTo(map);
```
Now that we have our map in place and the geographic data for our neighborhood in hand, create an Agentmap from the map:
```javascript
let agentmap = L.A.agentmap(map);
```
Now generate buildings and functional roads on the map based on the geographic data:
```javascript
agentmap.buildingify(map_data, bounding_points);
```
Now you can place agents on the map according to the rules of a custom agentFeatureMaker function. We'll use the built-in seqUnitAgentMaker,
which just assigns a number to each agent it generates in sequence, counting up from 0 to the number of agents you want, and places each agent
in the center of the unit with the same index as that number in the list of units. We'll make 50:
```javascript
agentmap.agentify(50, agentmap.seqUnitAgentMaker);
```
The simulation will start once we call `agentmap.run()`. The simulation is divided into consecutive "ticks", starting at tick 0.
On each tick, the following happens:
* The Agentmap runs its user-defined `Agentmap.controller()` (which we haven't defined yet)
* Each agent runs its own user-defined `Agent.controller()` (we'll leave that empty)
* Before and after each step an agent takes during the tick, it runs its own user-defined `Agent.fine_controller()` (we'll leave that empty too)
The number of ticks elapsed at any point in the simulation is set in `agentmap.state.ticks`, functioning as a kind of clock.
We can call `agentmap.pause()` to stop the simulation, during which the ticks elapsed won't change, and then `agentmap.run()` to continue it.
So, let's define a controller function for our Agentmap:
```javascript
agentmap.controller = function() {
};
```
What do we want to happen on each tick? That is, what will we put in the controller function's body?
A simple simulation will involve the agents moving to a random unit every 300 ticks.
So first, we will have the `Agentmap.controller` check if the current number of ticks is a multiple of 300,
as we only want anything to happen every 300 ticks:
```javascript
if (agentmap.state.ticks % 300 === 0) {
}
```
Then, if number of ticks _is_ a multiple of 300, we want to tell each agent to do something,
so we will set up a loop that operates on each agent:
```javascript
agentmap.agents.eachLayer(function(agent) {
}
```
Now, for each agent, we'll generate a random number between 0 and the total number of units, and
store the unit whose index is that number, its ID, and the coordinates of its center:
```javascript
let random_index = Math.floor(agentmap.units.count() * Math.random()),
random_unit = agentmap.units.getLayers()[random_index],
random_unit_id = agentmap.units.getLayerId(random_unit),
random_unit_center = random_unit.getBounds().getCenter();
```
Then we will schedule for the agent a trip to that unit's center at approximately 1 meter per tick:
```javascript
agent.scheduleTrip(random_unit_center, {type: "unit", id: random_unit_id}, 1, false, true);
```
We want the agent to move along whatever path it has scheduled at each tick, so we will add the following to the end of our
controller function, outside of the 300 tick condition:
```javascript
agent.moveIt();
```
Altogether, our Agentmap's controller will look like this:
```javascript
agentmap.controller = function() {
if (agentmap.state.ticks % 300 === 0) {
agentmap.agents.eachLayer(function(agent) {
let random_index = Math.floor(agentmap.units.count() * Math.random()),
random_unit = agentmap.units.getLayers()[random_index],
random_unit_id = agentmap.units.getLayerId(random_unit),
random_unit_center = random_unit.getBounds().getCenter();
agent.scheduleTrip(random_unit_center, {type: "unit", id: random_unit_id}, false, true);
}
}
agent.moveIt();
}
```
Finally, now that we've got our Agentmap, buildings and agents loaded, and a controller defined, we can add:
```javascript
agentmap.run();
```
Once we load our HTML document, the simulation should begin and we can watch our agents move around the neighborhood.
For all the features of Agentmaps, look through the more detailed documentation discussed in the next section.
# Documentation
Documentation for people who want to use AgentMaps is available at <https://noncomputable.github.io/AgentMaps/docs/index.html>.
Documentation for people who want to contribute to AgentMaps or understand its internals is available here <https://noncomputable.github.io/AgentMaps/devdocs/index.html>.
# Demos
You can find a simple demo of AgentMaps, similar to the basic walkthrough, live [here](https://noncomputable.github.io/AgentMaps/demos/simple/simple.html).
You can find a more substantial demonstration of AgentMaps live [here](https://noncomputable.github.io/AgentMaps/demos/epidemic/epidemic.html).
[Epidemic](https://noncomputable.github.io/AgentMaps/demos/epidemic/epidemic.html): Agents commute between different parts of a neighborhood while an infection spreads between them.
You can find the corresponding code under /demos in the gh-pages branch [here](https://github.com/noncomputable/AgentMaps/tree/gh-pages/demos).
# Feature Wishlist
I've been stuffed with other work, so it'd be really cool if anyone wants to come along and help make AgentMaps better and more useful.
Here are some things I think would be great for AgentMaps to have. If you add them before I do, I'll credit your name appropriately.
* Sidewalks: streets and sidewalks should be divided and made into distinct places that agents can distinguish and navigate between.
* Architectural Variety: buildings vary in size, dimension, and shape a lot—they're not all identical rectangles. Users should be able
to provide custom building specifications and AgentMaps should materialize them.
* For example, what if the GeoJSON the user provides contains a big park? The streets along it probably shouldn't be dotted with normal sized units—it should probably be one big unit itself!
* Urban Development: buildings change over time. Users should be able to specify these changes over time and AgentMaps should incorporate them coherently.
* Agent Diversity: agents are conveniently visualized with leaflet CircleMarkers and you can customize them a lot. But what if someone wants little human-shaped
sprites instead? Users should be able to specify Markers with custom images for the agents too.
# Authors
### Authors
* Andrew - came up with & built AgentMaps
# Acknowledgements
### Acknowledgements
I've only had a few extended conversations which involved me talking and thinking about this project outloud over the last few months, and those probably influenced how I went forward with it. The people I've had those discussions with are: