mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-23 14:23:14 +03:00
1ca1f571d0
simulation. not sure how to let people change this setting reasonably.
484 lines
20 KiB
Markdown
484 lines
20 KiB
Markdown
# Map-related design notes
|
|
|
|
## Lanes
|
|
|
|
It's time to model more things:
|
|
|
|
- multiple driving lanes, with possibly individual turn restrictions
|
|
- dedicated bus lanes
|
|
- lanes with parked cars
|
|
- bike lanes
|
|
- sidewalks
|
|
|
|
Each lane needs some geometry:
|
|
|
|
- center lines to draw agents on
|
|
- for sidewalks, use center line to to draw agents on the left and right sides?
|
|
- polygons to draw the lane and mouseover
|
|
|
|
Open questions:
|
|
|
|
- Can we assume all lanes are the same width?
|
|
- Seems wrong for many sidewalks especially
|
|
- Could be wrong for bike lanes, but could just assume it's a bike lane with a buffer
|
|
- Some lanes are immutable
|
|
- Sidewalks can't be changed to other types; they're raised with a curb
|
|
|
|
Some modeling questions:
|
|
|
|
- Where should expansion of roads into lanes happen?
|
|
- initial OSM conversion, adding more stuff to the proto?
|
|
- initial map_model::new loading, at least for development convenience
|
|
- same reason that turns aren't (yet) serialized
|
|
- Is it useful to model the entire road?
|
|
- the parent/child relation may be hard to maintain
|
|
- but lanes need to know their siblings
|
|
- maintaining directional sanity could be useful
|
|
- what's the UI for changing lane types?
|
|
- it's a bit arbitrary which lane should draw the yellow center lines
|
|
|
|
|
|
|
|
Initial design:
|
|
- "Road" becomes "Lane" with a type
|
|
- don't need to know sibling lanes yet
|
|
- arbitrarily, one lane might have extra bits/geometry for yellow center line markings
|
|
- ideally, get rid of one-wayness and original center points, and plumb along pre-shifted lines
|
|
- but due to the polyline problem (affecting both geom center line layer that agents follow, and polygons for drawing), can't do this. encapsulate the messiness at least.
|
|
- so, store one way and orig points and index, but have an accessor
|
|
- as a compromise, dont interpet OSM points on a one-way road as the center, but as the edge? this is proving hard to do.
|
|
|
|
Thinking about a new design:
|
|
- Much more general "Land" primitive that's just a nice polygon boundary for drawing/selection and one (or more, for sidewalks?) center lines for how to cross the space, with a notion of turns. It's what road is now, but way simpler data.
|
|
- Maybe the GeomRoad / DrawRoad split is a little confusing after all, since the layering just isn't perfect. figure out the polygon and centerline up-front, then ditch the other intermediate gunk.
|
|
- also ideally make one polygon for the road, not a bunch of individual pieces? but then we'd have to go triangulate later for opengl anyway
|
|
- enforce that all the polygons are nonoverlapping
|
|
|
|
## Representing map edits
|
|
|
|
Two reasons for edits:
|
|
- the basemap is wrong because of bad OSM data or heuristics
|
|
- here's a possible edit to A/B test
|
|
|
|
Types of edits:
|
|
- change lane type between driving, parking, biking
|
|
- sidewalks are fixed!
|
|
- some edits are illegal... parking lane has to be in a certain side... right? well, actually, dont do that yet.
|
|
- delete a lane (because the basemap is wrong)
|
|
- modify stop sign priorities
|
|
- modify traffic signal timings
|
|
|
|
How to visually diff edits?
|
|
- highlight them
|
|
- UI to quickly jump and see them
|
|
|
|
How to encode the edits?
|
|
- "Remove lane" is weird; how about per road, list the lane types? Then it's
|
|
almost kinda obvious how to plug into part of the current map making
|
|
pipeline.
|
|
- alright, let's really first think about road vs lane
|
|
|
|
Need to work through some edits to see how they affect downstream things. What
|
|
needs to be recomputed? How do we long-term serialize things like edits? How
|
|
can they even refer to things by ID if the IDs could change? What IDs might
|
|
change?
|
|
|
|
Alright, now we can be concrete -- when we have a road edit, what can be affected?
|
|
|
|
MAP LAYER:
|
|
|
|
- the road struct state (just list of children, really)
|
|
- dont want to blindly run all the road making code, since it'd double-add stuff to intersection
|
|
- delete old lanes, make new lanes
|
|
- how would IDs work? if we try to reuse the old ones, we might wind up
|
|
with gaps, or overflowing available space.
|
|
- trim lanes
|
|
- need to recalculate original lane_center_pts for all affected lanes
|
|
in a certain direction. tricky since they're two-sided; have to
|
|
restore just the original direction on it.
|
|
- recalculate turns, for the two intersections
|
|
- same ID problem
|
|
- recalculate some building front paths, maybe
|
|
|
|
CONTROL LAYER:
|
|
|
|
- recalculate two intersections
|
|
|
|
SIM LAYER:
|
|
|
|
- creating/deleting sidewalks is pretty easy
|
|
- SimQueues are associated with turns and lanes, but easyish to create/delete later
|
|
- should probably have a way to prevent mutations; maybe need to drain a lane of agents before changing it
|
|
|
|
UI:
|
|
|
|
- make a new DrawLane, DrawIntersection, etc
|
|
- update quadtrees
|
|
- would have to maybe update a bunch of plugin state (highlighting or
|
|
floodfilling or something), but since we know road editor is active, is easy!
|
|
|
|
|
|
|
|
Strategies:
|
|
- testing via equivalence -- reload from scratch should be equal to live edits
|
|
- will IDs make this very tricky?
|
|
- for things like sim and UI that hook on and have derived state, should we
|
|
always kinda lazily grab DrawRoads, SimQueues, etc? or immediately plumb
|
|
through deletes and inserts?
|
|
- is there a way to programatically record data dependencies or kinda do FRPish stuff from the start?
|
|
- could always blindly recalculate everything live, but man, that's gotta be slow
|
|
- maybe change constructors that take full map into incremental "hey, this road exists!" mutations. then just need to introduce deletions. in other words, embrace incremental mutability.
|
|
- assume the bbox doesn't change as a result of any edit
|
|
|
|
|
|
|
|
the ID problem:
|
|
- need determinism and deep equality checks for things. if we load a map from
|
|
scratch with edits, vs do a live shuffle, the IDs wont match up if they use a
|
|
slotmap.
|
|
- can we refer to things in more stable ways; no LaneID, but
|
|
RoadID+direction+offset. no Turn, but two... effectively lane IDs?
|
|
- maybe we can combine these ideas; use nondet slotmaps, but when doing
|
|
equality checks, dont use these IDs -- treat these IDs as memory addresses.
|
|
IDs for lookup and IDs for equality.
|
|
- what're the different things that need this?
|
|
- stable objects: building, intersection, road
|
|
- malleable
|
|
- lane (road, direction, offset, lane type)
|
|
- turn (src lane, dst lane)
|
|
- recurse and refer to full lane descriptions, or their temporary ID?
|
|
- ideally want to store things contiguously in memory
|
|
- ideally want a compact, easy thing to type quickly to debug.
|
|
- aka, ideally want a nice bijection from the persistent thing to numbers?
|
|
- actually, if we "leave room for" enough lanes per road and turns per intersection to begin with...
|
|
- can just replace existing IDs when we change something
|
|
- still have to mark things dead
|
|
- still have to watch out for dangling references
|
|
|
|
|
|
The changes needed:
|
|
- figure out the ID problem
|
|
- change existing code from big constructors to incremental adds
|
|
- exactly what layers and objects?
|
|
- implement incremental deletes
|
|
- try doing a live edit and comparing with from scratch
|
|
|
|
|
|
Going to start implementing part of this in a branch, just to get more detail.
|
|
|
|
- when there's a road edit, calculate the affected objects (road and all children, two intersections)
|
|
- implement a sanity check to make sure no dangling ref to old IDs
|
|
|
|
I think this is working so far. The vital question: is it too complicated? Is there a simpler way?
|
|
- simpler idea: retain more raw data, violently destroy road and intersection and make from scratch
|
|
- problem: it'd percolate, we need to keep old connected roads the same
|
|
- EVEN SIMPLER IDEA: stop trying to solve hard problems
|
|
- lane deletion is rare and a basemap-only edit; can mark it in the UI temporarily and omit in the next full load
|
|
- changing lane types is the main intended edit. what actual consequences does this have? filtering through the list earlier...
|
|
- change lane type
|
|
- recalculate all turns for two intersections
|
|
- the number of turns might go up or down
|
|
- control layer intersection policies then need updating
|
|
- sim needs to know about changed lanes and turns
|
|
- and a few easy edits in the UI layer too
|
|
- changing lane direction might be a little more complicated, but NOT BY MUCH
|
|
|
|
so, I think the steps:
|
|
= see what's useful from this branch, bring it to master (encapsulating the driving state stuff)
|
|
= ditch TurnIDs; have a BTreeMap of src/dst (LaneID, LaneID)
|
|
= add a mutate_lanes() and replace_turns() to all the appropriate layers
|
|
|
|
Cool, good enough to start. whew.
|
|
|
|
### Looking at this problem again
|
|
|
|
raw_map to map is expensive -- so much that we have to precompute it. For the simple edits (change lane type and modify intersection policy), can we do a cheap live update?
|
|
|
|
MAP LAYER:
|
|
|
|
- initial Road stores lane_specs, but doesnt use the lanetypes yet
|
|
- halfmap uses lane types: is_border, make_all_turns, turn lookup idx
|
|
- stop signs and traffic signals assigned last-minute, very easy to override in map layer
|
|
- pathfinder graph
|
|
|
|
SIM LAYER (ignore for now, just ban most live edits):
|
|
|
|
- parking sim needs to know about lane types
|
|
- block some edits
|
|
- cant modify turns while intersection has any requests or accepted
|
|
- cant change lane type while anybody is on it
|
|
- or if parking spot is reserved
|
|
- paths are also affected
|
|
|
|
EDITOR LAYER:
|
|
|
|
- ooh, luckily DrawLanes aren't batched! should be relatively easy.
|
|
|
|
equivalence test...
|
|
- option 1: load a map from scratch with edits, compare to making live edits, do PartialEq and meld
|
|
- option 2: always apply edits as last step of initial map loading. always reset map to canonical form before applying edits.
|
|
|
|
|
|
So how to structure things?
|
|
|
|
- dont ask for individual changes; mutate a MapEdits object and ask map, sim, editor layer to recalculate everything needed.
|
|
- lane IDs will never change; just store LaneID -> LaneType overrides.
|
|
- might have to figure out original OSM-based lanetype by recalculating
|
|
- applying map edits always takes a BEFORE (possibly empty) and AFTER set
|
|
|
|
## Notes on King County GIS datasets
|
|
|
|
- TODO: https://data-seattlecitygis.opendata.arcgis.com/datasets/channelization
|
|
|
|
- https://data-seattlecitygis.opendata.arcgis.com/datasets/street-signs
|
|
- could figure out stop sign orientation from this
|
|
|
|
- https://data.seattle.gov/dataset/Street-Pavement-Edge-1999/rm29-895w
|
|
- good base geometry
|
|
|
|
- https://data.seattle.gov/Transportation/Traffic-Lanes/v5x7-g7qz
|
|
- some streets missing
|
|
- info on turn lanes
|
|
|
|
- https://data.seattle.gov/Transportation/Intersections/e7db-mhd7
|
|
|
|
- https://gis-kingcounty.opendata.arcgis.com/datasets/transit-signals-for-king-county-metro--tsp-signals-point?geometry=-122.315%2C47.641%2C-122.291%2C47.646
|
|
- description says number of phases
|
|
|
|
- https://www.wsdot.wa.gov/mapsdata/tools/trafficplanningtrends.htm
|
|
- small start to demand data
|
|
|
|
## Speeding up map loading
|
|
|
|
- can we use quadtrees for the expensive building/sidewalk matching?
|
|
- awkward to bring a rendering-layer concept in; dont otherwise care about lane polygons
|
|
|
|
## Neighborhoods
|
|
|
|
It's hard to zoom out and quickly pinpoint where interesting things (A/B diffs,
|
|
traffic jams, suspicious silence) are happening. What if we could optionally
|
|
collapse a region into a big colored polygon and just display quick stats on
|
|
it?
|
|
|
|
- defining them
|
|
- what do they include?
|
|
- a polygon capturing buildings, lanes, etc
|
|
- what happens when the polygon only partially contains an object?
|
|
- is it a border thing? makes sense for lanes, not for buildings
|
|
- border lanes could be used for some kind of in/out flow
|
|
- do they have to fully partition the map?
|
|
- they should at least be disjoint
|
|
- how to define them?
|
|
- the seattle neighborhood definitions are seemingly way too large
|
|
- could manually draw them, but including buildings carefully is sort of hard
|
|
- we have the automatic parcel grouping stuff for coloring... could it help?
|
|
- could find max-cuts to spot important border roads and the neighborhoods they connect
|
|
- summary stats
|
|
- cars parked, open parking spots, moving peds, moving cars,
|
|
stuck cars, busses present, number of agents with A/B test
|
|
divergence...
|
|
- this can start to force me to be mildly performant.
|
|
precompute what objects are in each polygon, then have a
|
|
summary thing collect stats every few seconds when shown?
|
|
- do these need to be objects in the GUI? at first no, just make a
|
|
plugin draw them, but eventually, yes. they should probably be a
|
|
map_model concept.
|
|
|
|
## Border nodes
|
|
|
|
No matter how things are sliced, we always have to cut off roads somewhere,
|
|
unless we simulate a continent. :) So the idea of border nodes is to
|
|
start/terminate these cut-off roads with a special intersection where traffic
|
|
can begin or end.
|
|
|
|
- rendering?
|
|
- ideally the nodes would actually be at the boundary of the map
|
|
- https://wiki.openstreetmap.org/wiki/Osmosis/Detailed_Usage_0.47#Area_Filtering_Tasks
|
|
has some flags to explore
|
|
- some special color or symbol?
|
|
- detection
|
|
- for oneways this is easy, but two-ways look like dead-ends
|
|
- how to distinguish actual dead-ends?
|
|
- get osmosis to also output which OSM ways were clipped?
|
|
- don't see how to do this
|
|
- manually marking them?
|
|
- draw the FSM for cars/peds
|
|
- trips starting/ending at border nodes short-circuit some steps
|
|
|
|
What about border nodes where the sidewalk can cross over, but the driving lane
|
|
leads nowhere? Maybe instead of having a special intersection policy, we have
|
|
turns to nowhere? Sort of depends how the border node is defined -- do we have
|
|
long skinny things jutting out, or not?
|
|
- don't add any turns, even if we could
|
|
|
|
OK, now adapt the control and sim layers to handle border nodes...
|
|
|
|
Adapt trips / pathfinding. It has to be an explicit origin/goal.
|
|
|
|
start/end for trips right now is buildings. Needs to be building or a border
|
|
node. And driving also gets more complicated -- can start from a parked car or
|
|
a border node, and end by parking near a building or going to a border node.
|
|
|
|
### The master FSM
|
|
|
|
This doesn't belong in maps.md, but related to border nodes, so for now...
|
|
|
|
There's ultimately a big state machine for trips that's awkwardly hiding in the
|
|
code and slowly being exposed by stuff like the higher-detail pathfinding.
|
|
|
|
- possible starts:
|
|
- ped exits building
|
|
- ped appears at border node
|
|
- car appears at border node
|
|
|
|
- possible ends:
|
|
- ped enters building
|
|
- ped vanishes at border node
|
|
- car vanishes at border node
|
|
|
|
- the stuff in the middle
|
|
- ped crosses front path from building to sidewalk spot
|
|
- ped crosses front path from sidewalk spot to building
|
|
- agent crosses a lane normally
|
|
- agent crosses a lane contraflow
|
|
- agent makes a turn
|
|
- unparking a car from a spot
|
|
- parking a car at a spot
|
|
- (soon) lanechanging
|
|
- wait for a bus
|
|
- enter a bus
|
|
- ride a bus
|
|
- exit a bus
|
|
|
|
- Roaming around looking for parking dynamically and lazily updates the front of this plan
|
|
|
|
- Buses are weird
|
|
- it's annoying they don't have a trip (and don't work with the route plugin today)
|
|
- they share some states (the mechanical driving ones)
|
|
- and have a few of their own (deboard people, board people)
|
|
- dynamic plan expansion also happens when departing from a stop; since
|
|
storing the entire cycle would be weird
|
|
- Parked cars aren't really part of this giant state machine
|
|
|
|
Then the code for these little transitional states can be less weirdly special cased, maybe.
|
|
|
|
How does initial plan formation and mode choice work? Could have a round of
|
|
pathfinding for each possible mode, or could search a single more abstract
|
|
action graph. moves from walking on this sidewalk? oh we own this car, could
|
|
choose to unpark it, then drive somewhere. Everything has time cost.
|
|
|
|
This also supercedes the Event thing in sim and makes testing potentially WAY cooler.
|
|
|
|
## Pathfinding
|
|
|
|
How do we natively wind up with a list of PathSteps (normal lane, contraflow
|
|
lane, turn) without doing the terrible stitch-together later thing?
|
|
|
|
- easy option: the nodes in the graph we search become PathSteps. expansion is easy.
|
|
- is it slightly inefficient that we could loop back and forth on
|
|
sidewalks? Should be able to prevent it by not making that a way to
|
|
expand.
|
|
- maybe less weird option: nodes are (Lane, Intersection). or even more
|
|
accuratelyish, (Lane, dist_along), so we can start and end anywhere.
|
|
- When stitching together the path, a pair of lanes is a turn.
|
|
Otherwise, cross forwards or backwards.
|
|
|
|
## Road query refactor
|
|
|
|
What we have right now...
|
|
|
|
Road
|
|
pub fn find_sidewalk(&self, parking_or_driving: LaneID) -> Result<LaneID, Error>
|
|
pub fn find_driving_lane(&self, parking: LaneID) -> Result<LaneID, Error>
|
|
pub fn find_driving_lane_from_sidewalk(&self, sidewalk: LaneID) -> Result<LaneID, Error>
|
|
pub fn find_parking_lane(&self, driving: LaneID) -> Result<LaneID, Error>
|
|
|
|
pub fn get_opposite_lane(&self, lane: LaneID, lane_type: LaneType) -> Result<LaneID, Error>
|
|
pub fn get_siblings(&self, lane: LaneID) -> Vec<(LaneID, LaneType)>
|
|
|
|
Map
|
|
pub fn get_driving_lane_from_bldg(&self, bldg: BuildingID) -> Result<LaneID, Error>
|
|
pub fn get_driving_lane_from_sidewalk(&self, sidewalk: LaneID) -> Result<LaneID, Error>
|
|
pub fn get_sidewalk_from_driving_lane(&self, driving: LaneID) -> Result<LaneID, Error>
|
|
pub fn get_driving_lane_from_parking(&self, parking: LaneID) -> Result<LaneID, Error>
|
|
|
|
pub fn building_to_road(&self, id: BuildingID) -> &Road
|
|
|
|
Some themes...
|
|
|
|
- have some lane, want the nearest lane of some type(s).
|
|
- The source doesn't really matter -- we want as close as possible, but there's no requirement of adjacency at all.
|
|
- If the source/dest is a sidewalk, some weird handling for one-ways...
|
|
|
|
## Dist along mismatches
|
|
|
|
Concrete examples:
|
|
- House path on a sidewalk that's slightly longer than the driving lane, so can't spawn a bike
|
|
- Parking lane longer than driving lane, so can't make a car start
|
|
|
|
Solutions
|
|
- Don't blindly copy dist_along between lanes. Do perpendicular line segment check to find the proper value.
|
|
- But is this allowed to fail? Or just have weird edge cases and round
|
|
up/down to the beginning/end of the other lane?
|
|
- Embrace failure
|
|
- Silently erase agents/trips when they fail
|
|
- With better up-front planning, can maybe filter out the trip from the beginning
|
|
- But this is hacky and doesn't capture user intention
|
|
- Erase problems
|
|
- Filter out houses, parking spots, etc that don't match other lanes
|
|
- This seems too aggressive...
|
|
|
|
Start by detecting all the possible problems in the geom validator plugin. Then
|
|
probably go with the first solution, after understanding what the most
|
|
egregious edge cases look like.
|
|
|
|
## Debugging turns
|
|
|
|
- How can we make a TurnID into an easily-typable thing to warp to it?
|
|
- hash?
|
|
- not actually unique, but good enough probably? could be confusing one day ;)
|
|
- make a helper that's exhaustive
|
|
- concat?
|
|
- ideally it'd be easily bijective, but since this is just for debug,
|
|
it's not so terrible to brute-force to lookup
|
|
- hash + base 36 is still too much... 1dj33llg4ei37
|
|
|
|
- lets just separately index them. shrug. ideally part of the TurnID, but oh well.
|
|
- How can we display the turn itself in response to warp?
|
|
- need to poke turn cycler from warp?
|
|
|
|
## Faster pathfinding
|
|
|
|
https://pdfs.semanticscholar.org/36d1/b4ec6a4a2823e9c875318f1952df4abf4876.pdf
|
|
|
|
- do a bunch of pathfinding queries normally
|
|
- then look for common shared sequences
|
|
- cache those paths, then anytime normal pathfinding later hits a node, be able to jump to any destination with known cost
|
|
- but will the better heuristic to the goal make us actually search those?
|
|
|
|
- or just use intersections between big roads as these landmarks, precompute paths between them
|
|
|
|
contraction hierarchies: https://github.com/cc-routing/routing/wiki/Contraction-Hierarchies
|
|
|
|
https://github.com/aquavit/Project-OSRM/wiki/OSRM-normalized-file-format
|
|
|
|
https://github.com/deliveroo/osrm-rs
|
|
|
|
- for transit routing: https://www.microsoft.com/en-us/research/publication/round-based-public-transit-routing/
|
|
|
|
## Traffic signal data
|
|
|
|
https://wiki.openstreetmap.org/wiki/Proposed_features/Traffic_Signals
|
|
https://wiki.openstreetmap.org/wiki/Proposed_features/Set_of_Traffic_Signals
|
|
https://wiki.openstreetmap.org/wiki/Tag:highway%3Dtraffic_signals#Complex_intersections
|
|
https://sumo.dlr.de/wiki/Simulation/Traffic_Lights
|
|
https://wiki.openstreetmap.org/wiki/Proposed_features/Traffic_Signal_Timings
|
|
https://connectedsignals.com/
|
|
|
|
## CARLA project
|
|
|
|
http://carla.org/
|
|
https://www.vectorzero.io/
|
|
http://www.opendrive.org/download.html spec
|
|
https://github.com/carla-simulator/carla-map-editor
|