惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
NISL@THU
NISL@THU
WordPress大学
WordPress大学
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threat Research - Cisco Blogs
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
Security Archives - TechRepublic
Security Archives - TechRepublic
有赞技术团队
有赞技术团队
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
V
V2EX
G
GRAHAM CLULEY
TaoSecurity Blog
TaoSecurity Blog
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
Latest news
Latest news
The Hacker News
The Hacker News
aimingoo的专栏
aimingoo的专栏
T
Troy Hunt's Blog
S
Schneier on Security
I
Intezer
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threatpost
爱范儿
爱范儿
The Register - Security
The Register - Security
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
博客园 - 聂微东
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
美团技术团队
B
Blog RSS Feed

Steve Bennett blogs

Whenever: exploring times and places in film and books Host your own vector tile server on Glitch Building TinyMap: an itty bitty collaborative mapping tool You might not need PostGIS: streamlined vector tile processing for big map visualisations OpenStreetMap vector tiles: mixing and matching engines, schemas and styles 2015’s proudest moments Your own personal National Map with TerriaJS: no coding and nothing to deploy After the hackathon: 4 classic recipes OpenTrees.org: how to aggregate 373,000 trees from 9 open data sources Cycletour.org: a better map for Australian cycle tours Normalize cross-tabs for Tableau: a free Google Sheets tool 7 reasons to release that government dataset The Data Guru in Residence Chromecast in the real world: six casting workflows Web map projections: the bare minimum you need to know Multivariate binary symbol maps with TileMill. The Australian’s menacing editorial Cycletouring and OpenStreetMapping: a beautiful symbiosis Git: what they didn’t tell you One week of Salt: frustrations and reflections. Super lightweight map websites with Github Digital humanities for beginners: get started with the Trove API Trello Tennis Terrain in TileMill: a walkthrough for non-GIS types A TileMill server with all the trimmings Forget trying to remember your servers’ names! Anonymous longitudinal surveys with LimeSurvey Windows red cross errors scam What I learned at e-Research Australasia 2012 A pattern for multi-instrument data harvesting with MyTardis Getting started with Chef on the NeCTAR Research Cloud How OData will solve data sharing and reuse for e-Research 10 things I hate about Git Semantic Google keywords Improving on the “administration rights required” workflow Why is buying stuff from eBay so complicated? Introducing: Cooking for engineers New Gmail feature: auto mailing list management Penny Auctions – a bit of analysis Hello world!
Alternative Earth: a procedurally generated map using vector tiles
steveko · 2020-01-03 · via Steve Bennett blogs

I make maps. A lot of maps. Almost always of somewhere on Earth. It was time for a change.

The challenge: a procedurally generated map of this alternative Earth, produced on the fly, vector tile by vector tile. An easier approach might be to, say, first generate some continents and coastlines, then erode some mountains with rivers and dot towns in appropriate locations. But I wanted to have a tiny server with no global context. When the user’s browser requests the tile at ZXY coordinates 11/1976/1368, it quickly generates and responds:

Screen Shot 2020-01-03 at 10.00.29 am

So many challenges.

Dynamic vector tile generation

First, we need a server which can generate vector tiles on demand. We use NodeJS Express, with vector-tile specific libraries: vt-geojson, tilebelt, vtpbf.

  1. Find the bounding box (in lat/longs) covering the area for the tile requested.
  2. Generate data that covers that area, in GeoJSON.
  3. Convert the GeoJSON to vector tiles.
  4. Extract the one vector tile requested
  5. Convert it to PBF (Mapbox vector tile format) and send it back.

It’s a bit inefficient to keep generating multiple vector tiles and only selecting one, but it’s simple. :)

It basically looks like this:

Screen Shot 2020-01-03 at 10.10.22 am

I’m using Glitch to host this server: glitch.com/~procmap

Deterministic generation

The big challenge for this kind of procedural generation is to make everything absolutely deterministic, even when it looks “random”. If a formula says the location of a given town is [123.1,-45.7] then it must always be that, no matter which tile the town is generated as part of. The basic strategy goes like this:

  • Use the number-generator library to generate pseudo-random numbers.
  • Before generating any random numbers for a given entity (town, road etc), seed the library with the hash of a string that uniquely identifies that entity.
  • Make a lot of mistakes and get very confused.

Towns on grids

If we were generating the whole world in one go, we could iterate 1,000,000 times, dropping a town in a random location each iteration. Instead, we need to create a global, immutable structure that sort of always “exists” and relate each town to that structure. A simple way to do this (actually, the only one I could think of!) is to use a grid that covers the whole world, where each grid point is one town. That grid point is also the seed for the random number generator, and hence all the town’s properties (name, size etc) derive from its position.

Screen Shot 2020-01-03 at 10.23.33 am

How good this looks in practice comes down to how well we disguise the grid. By simply pseudo-randomly displacing each town up to half a grid coordinate in any direction, it suddenly looks much better:

Screen Shot 2020-01-03 at 10.25.39 am

Actually managing the grid was a bit fiddly. The random displacement means that we don’t know exactly which towns will end up within the vector tile we’re generating for. So we have to generate all towns in or next to the vector tile, then crop the ones that end up falling outside it.

Interesting towns

All the properties are just a question of finding formulas with attractive distributions and representing them appropriately. For instance, the size of a town (on a scale of 1-5) is: Math.ceil(random() * random() * random() * 5).

That is, a cubic distribution so there are far more tiny towns than big ones.

We generate town names with the fake-town-name library, which I wrote for this purpose. It’s pretty simple:

  1. Take a random starting fragment (eg, Ton-, Hyde-, Lang-, Stam-)
  2. Add a random ending fragment (eg -bury, -well, -mont, -rick)
  3. Sometimes add a prefix (eg Outer, North, East, or nothing)
  4. Sometimes add a suffix (eg Creek)
  5. Hence create Outer Tonbury, North Hydewell, East Langmont Creek, or Stamrick.

Roads

The nice thing about roads is that generally they connect two towns, so we have a great place to start from. For each town, look at all the neighbouring towns (the grid makes this very easy!), and consider whether to connect it with a road.

However, there is a trap: our decision for connecting A to B must be the same as that for connecting B to A – and following exactly the same route. So, we use the western-most (northern-most to tie-break) of the two towns as the “A” to seed the random number generator.

The formula I came up with takes a couple of things into account:

  • How near are the towns geographically (closer towns are more likely to connect)
  • How big are the towns (a bigger town has more roads)
  • A general ratio for the number of roads

Screen Shot 2020-01-03 at 10.41.55 am

There’s also a “size” property which is determined by the sizes of the towns at either end: bigger towns cause bigger roads betweens them.

Screen Shot 2020-01-03 at 10.47.47 am

Wiggles

Now, straight roads are boring. We can make a more interesting road by simply adding some extra vertices along the way. We can use a kind of L-system to do this:

  • Between every two vertices, create a midpoint vertex.
  • Randomly displace that vertex by a distance relative to the length of the segment and a “wiggliness” coefficient.
  • Repeat as many times as required (“complexity”).

The wiggliness coefficient is affected by the size of the road (smaller roads between small towns are more wiggly), and the complexity is affected by the zoom level (so we aren’t wasting CPU cycles making very complex roads that can’t even be seen.

Screen Shot 2020-01-03 at 10.48.50 am

Water

Lakes and coastline are quite challenging. If we simply randomly decide that a given area is water or land, we will probably end up with an unattractive pattern of many fragmented lakes.

We start with the same grid structure as towns, but on a bigger scale. Next, we use Simplex Noise as a method for determining whether there is water somewhere. It naturally produces big clumps that work well for this purpose.

Screen Shot 2020-01-03 at 10.59.05 am.png

Perfect. Let’s distort this grid, too.

Screen Shot 2020-01-03 at 12.32.51 pm.png

The one thing missing is interesting coastlines/lake edges. Let’s start by extending each straight edge into a triangle, with the tip a random location somewhere in the neighbouring cell.

Screen Shot 2020-01-03 at 11.06.19 am.png

Then we can apply the same complexification algorithm that we used for roads.

Screen Shot 2020-01-03 at 11.07.11 am

Finally,  by using slightly different parameters to these algorithms we can generate “deep water” and “beach” layers.

Screen Shot 2020-01-03 at 11.07.36 am.png

Forests are water

Forests are literally created using the same process as water, but slightly different parameters.

Screen Shot 2020-01-03 at 11.10.01 am copy

Limitations

One obvious limitation in this approach is that the layers we generate don’t interact with each other. We’re just slapping a lake over the top of the map and hoping no one notices that there are towns under there. Labels get cut off, highways mysterously disappear into the water.

Another problem is that it’s tricky to make things that require more than local context. We’d like major highways to purposefully connect distant cities, passing through small towns on the way. Not just starting and stopping at random. We’d like railways that similarly have an overall direction, and don’t meander too much. Most of all, we’d like streams that flow in one direction, continually merging with other streams to become rivers, eventually emptying into lakes and seas. I’m not sure how to achieve that.

There is also a reversability problem. We can generate a random location for a town from grid position, but it would be good to be able to find the grid position from the generated location. It would be nice to be able to search for a town name without brute force.

Finally, there are challenges with the vector tiling process. For instance, by restricting our search for roads to towns within the current tile, we miss out on roads that merely cross the tile without originating or ending within it. For instance, here the town from Katefields to Ilwick doesn’t show up in the bottom left tile, and the road from Morganburn northeast is not shown in the bottom right tile. We can reduce the visibility of this issue by using bigger vector tiles, but probably we just need to search a bit wider for towns.

Screen Shot 2020-01-03 at 12.37.48 pm

All up, I’m pretty happy with the result. It’s been a lot of fun so far. :)

Have a play at stevage.github.io/alt-world!

Screen Shot 2020-01-03 at 11.14.27 am