Text Mapper

Text Mapper is a web app to quickly generate a map based on a textual description of the map. The result is an SVG file and therefore scalable.

Check it out:

The source is available.

2018-07-03 Rotating Text Mapper Maps

A user sent me a little NodeJS script to rotate a map in order to get “stacked columns”.

I wonder whether I should add a different hex stacking option to Text Mapper.

Tags:

Add Comment

2018-06-14 Wrote a Bot

I wrote a Mastodon bot: @textmapper. It posts a Text Mapper map once a day.

It was surprisingly easy to do! I’m still confused by how easy it was. I was expecting some bugs. It... just worked!? I had great libraries to work with: Mastodon.py, CairoSVG, and all the other things I used.

I’m also using the Software Wiki to keep track of ideas.

Tags:

Add Comment

2018-03-29 Generating Islands

Vague thoughts on randomly generating a map for a pirate campaign, or a colonialism, slavery, the spice trade, the silver train (and robbing it), and all that.

Think about the Haitian Revolution. Or Ching Shih. Pirates! The Philippines.

I think the first problem I usually have is island formation. Specially if we want atolls, or a chain of islands. Are we going to model a hot spot like Hawaii? Or something like the Caribbean, or the Pacific Rim? How many hexes per island are we going to aim for? If it is going to be just three or four per island, then questions of altitude are moot. But if we want to generate islands like Cuba and Tahiti, the result would be very different.

Tags:

Add Comment

2018-03-12 Describing Hexes

On Google+, Zak asked for a tool that would generate both a map and a description of said map.

Well, I have Text Mapper which creates beautiful maps, but I have no random tables to describe those maps. But I can work on that. And perhaps you can help?

If you feel like contributing to this, feel free to leave your contributions in a comment on this blog, or tag me on Google+ or Mastodon, or contact me some other way.

Here’s my example map:

Image 1 for 2018-03-12 Describing Hexes

The map description looks like this:

0101 dark-green trees village
0102 light-green bushes
0103 light-green bushes
0104 light-green bushes
0105 light-green bushes
0106 light-green forest-hill
0107 light-grey mountain
0108 white mountains cliff1
0109 white mountain
0110 light-grey mountain
0201 dark-grey swamp
0202 light-green bushes
0203 light-green bushes
0204 green forest
0205 light-green fir-forest
0206 light-green firs thorp
0207 white mountain cliff1
0208 white mountain
0209 light-grey mountain
0210 grey swamp
...

More...

If you want, you can copy and paste this description into Text Mapper and it should regenerate the map shown above.

The random tables look like this:

;mountain
1,the air up here is cold
1,snow fields make progress difficult
1,there is a hidden meadow up here, hidden from view from below
1,steep cliffs make progress practically impossible without climbing gear
1,nothing grows up here except for a few patches of lichen on gray rocks
...

More...

As you can see, the format is simple:

  • a semicolon introduces a new table
  • each line in a table consists of the relative probability, a comma, and some text
  • if the text has something in square brackets, that is in turn looked up in a table

I tried to use the format used by Abulafia.

And it works!

Take a look: Hex Describe.

Tags:

Comments on 2018-03-12 Describing Hexes

The work is ongoing. Let me know if you want to contribute.

– Alex Schroeder 2018-03-14 12:50 UTC


I think I’m done with all I wanted to do. The monster entries were based on the monsters in my H&H Ref Guide.

– Alex Schroeder 2018-03-14 22:14 UTC

Add Comment

2017-07-07 Text Mapper Command Line

I recently posted some examples of how to Traveller Subsector Generatorfrom the command line. Here’s how to do the same thing for Text Mapper.

xmllint allows us to extract text from XML and HTML documents. On a Debian system, it’s part of libxml2-utils.

Generate a text file with a 20x20 alpine wilderness map:

perl text-mapper.pl get /alpine 2>/dev/null | xmllint --html --xpath '//textarea/text()' - > random-alpine-map.txt

You’ll note that at the very end it contains the seed value.

You can regenerate the same map using this seed:

perl text-mapper.pl get "/alpine?seed=1499413794" 2>/dev/null | xmllint --html --xpath '//textarea/text()' - > 1499413794.txt

You can also modify the width and breadth of the map:

perl text-mapper.pl get "/alpine?width=10&height=5" 2>/dev/null | xmllint --html --xpath '//textarea/text()' - > random-alpine-map.txt

Let’s define an alias to handle the encoding of the map for us:

alias encodeURIComponent='perl -pe '\''s/([^a-zA-Z0-9_.!~*()'\''\'\'''\''-])/sprintf("%%%02X",ord($1))/ge'\'

Make some changes to the text file generated above using a text editor and generate the updated map:

perl text-mapper.pl get --header 'Content-Type:application/x-www-form-urlencoded' --method POST --content map=$(cat 1499413794.txt|encodeURIComponent) /render 2>/dev/null > 1499413794.svg

You can use svgexport to generate a PNG image, if you want.

First, install it:

npm install svgexport -g

You need to tell it what quality to use when exporting. I use 100% for PNG files; I’d use less for JPG files.

svgexport 1499413794.svg 1499413794.png 100%

I guess I have all the tools to create a Twitter bot? ;)

Tags:

Add Comment

2017-06-09 The Purpose of a Map

“A map is a symbolic depiction emphasising relationships between elements of some space,” says Wikipedia. But Sophie Lagacé goes further than. In Mapping the Veins of a World she talks about the importance of water shaping the map, the importance of rivers and “a multitude of smaller streams flowing towards the river like blood vessels or veins in a leaf.”

I agree with the beauty and I importance of water! I spent way to much time writing the river and mountain code for my Alpine Map Generator – and the documentation view where I get an image of the various stages so that I can check how the water flows.

At the end of her blog post, Sophie adds: “To be truly useful, the map should have an impact on the adventures. Otherwise it’s just a page decoration, and all to often a mediocre one.”

And that got me thinking. My maps are a way to structure access to locales, to explain realms of influence, borders.

In my current game I encouraged people to transport goods along the major river, and to build a road, and fords are hard to find so perhaps in the future there will also be a bridge to be built. This is the slow backdrop developing because people need to spend gold for xp in the game, so infrastructure built by players is a thing.

But I haven’t progressed much further than that. A lack of timber wouldn’t affect the game, more swamps wouldn’t affect the game, control of a mountain pass wouldn’t affect the game and I feel it should – but I also don’t want to get bogged down in details.

Perhaps finding that balance between geography as backdrop and geography as an anchor for adventure is what I’m struggling to find. Perhaps I need to have a table for random monthly events based on the surrounding territories.

  • Summer and forests adds the possibility of a forest fire
  • Winter and lakes adds the possibility of creatures crossing the ice
  • Spring and rivers adds the possibility of flooding
  • Mountains and winter adds the possibility of avalanches cutting you off for months
  • Summer and swamps adds the possibility for disease striking the local settlement

And perhaps some rules that are always in effect:

  • There is always timber trade from forested areas along rivers to settlements without forests
  • Mountains in winter are impassable

I’d like to return to the question of usefulness of a map. What impact does it have on adventures?

Perhaps a look at the actual maps people use at their gaming table would help. Why am I not seeing a lot of people simply using Google Maps? I think that’s because it doesn’t highlight the things gamers need:

  • adventuring locales are not highlighted
  • modern settlements are not what many of us are looking for
  • I need simple terrain to describe the area: forests, rivers, mountains, not the fractal mix of reality
  • small but important features are missing: menhirs, stone circles, wells, statues – these all exist at different zoom levels, but at the table I just want a single map

So why not the opposite extreme: what are the benefits of a point crawl, a graph of interesting locations like in the old text adventures?

  • the edges of the graph can be labelled with the time it takes to travel from one node to the other
  • no time wasted counting hexes or squares or using a ruler
  • who cares about miles per hour, miles per day, whether horses are really faster than humans and all that?
  • given the question of how to travel from A to B it still provides all the interesting locations between A and B in the right order, and it provides alternative routes

Things that the graph doesn’t provide:

  • opportunities to get lost
  • a way to make short cuts because nobody really knows what lies between the nodes – it’s undefined
  • there is no way to zoom out and identify important strategic and economic positions based on geography; in a point crawl, you must decide upon these locations and create a node for them

This last point is perhaps the most interesting to me because it allows me to discover more details in an existing map. The map is itself more than the set of its elements. If there is a valley here and a valley there, then perhaps these are also spheres of political influence. If there is a forest here and a settlement there, then perhaps there is a constant flow of timber between the two.

When looking at a setting map or regional wilderness map these days, I ask myself how much of a difference this map would make at my table:

  • Are the distances important in my game?
  • Is the terrain important in your game?
  • Are rivers and mountain ranges important obstacles?
  • Are the locations mines, pastures, or forests important assets?
  • Is the distribution of settlements important in terms of politics?
  • Is there an opportunity to get lost, take risky short cuts, claim unsettled terrain?

The answers to these questions determine the amount of information I would like to see on a map.

If you’re looking for maps, there are two great Google+ communities I know: Map-Making in Games, The Library of Gaming Maps, and there’s Reddit: /r/mapmaking/, /r/papertowns/, /r/oldmaps/, and Cartographers’ Guild.

Tags:

Add Comment

2016-08-16 Alpine Map Generator

I’ve been working on the Alpine map generator some more. It uses Text Mapper to render the output into an SVG image and it uses the Gnomeyland icons by Greg MacKenzie so it looks nice. Sadly, I’ve developed a pathetic obsession about getting it “right”. To illustrate my obsession and to help me fight it, let me document what I’m talking about. This is a “bug” I just fixed.

Here’s the old map:

The old map

What’s wrong you ask? I was confused by the canyon carved into the mountains from lake 17.04. I expected it to flow into 18.05. Let’s check the height map before lakes start flooding, looking for an outlet:

The initial setup with arrows pointing where the water wants to flow

Why would the river flow from 16.03 (height 7) to 15.04 (height 9)? This makes no sense.

So I studied my debug logs:

Lake started with 1704
Candidates: 1704
Looking at candidate 1704
River now: 1704
A neighbor of 1704 is 1804 with target 1704
Adding 1804 to our lake because it empties into our lake; the river leading here: 1704 1804
A neighbor of 1704 is 1603 with target 1704
Adding 1603 to our lake because it empties into our lake; the river leading here: 1704 1603
A neighbor of 1704 is 1803 with target 1704
Adding 1803 to our lake because it empties into our lake; the river leading here: 1704 1803
A neighbor of 1704 is 1703 with target 1704
Adding 1703 to our lake because it empties into our lake; the river leading here: 1704 1703
A neighbor of 1704 is 1705 with target 1704
Adding 1705 to our lake because it empties into our lake; the river leading here: 1704 1705
A neighbor of 1704 is 1604 with target 1704
Adding 1604 to our lake because it empties into our lake; the river leading here: 1704 1604
Candidates: 1804 1603 1803 1703 1705 1604
Looking at candidate 1804
River now: 1704 1804
Adding lake 1805 to our candidates: 1603 1803 1703 1705 1604 1805
A neighbor of 1804 is 1905 with target 1805
Adding 1905 to our lake because it empties into our lake; the river leading here: 1704 1804 1905
A neighbor of 1804 is 1904 with target 1803
Adding 1904 to our lake because it empties into our lake; the river leading here: 1704 1804 1904
Candidates: 1805 1603 1803 1703 1705 1905 1604 1904
Looking at candidate 1603
River now: 1704 1603
A neighbor of 1603 is 1602 with target 1702
Adding 1602 and 1702 to our lake, but need to explore
We flowed back into the lake via 1704 1603 1602 1702 1703
... 1702 is a new candidate with river: 1704 1603 1602 1702
... 1602 is a new candidate with river: 1704 1603 1602
Back at 1603 with river 1704 1603
A neighbor of 1603 is 1504 with target 1505
Adding 1504 and 1505 to our lake, but need to explore
Adding 1506 to our lake and keep exploring
Adding 1606 to our lake and keep exploring
Adding 1707 to our lake and keep exploring
Adding 1807 to our lake and keep exploring
Adding 1808 to our lake and keep exploring
Adding 1909 to our lake and keep exploring
Adding 2009 to our lake and keep exploring
Adding 2010 to our lake and keep exploring
Adding 2110 to our lake and keep exploring
Adding 2210 to our lake and keep exploring
Adding 2211 to our lake and keep exploring
Adding 2212 to our lake and keep exploring
Adding 2313 to our lake and keep exploring
Adding 2413 to our lake and keep exploring
Adding 2514 to our lake and keep exploring
Adding 2614 to our lake and keep exploring
Adding 2715 to our lake and keep exploring
Adding 2716 to our lake and keep exploring
Adding 2816 to our lake and keep exploring
Adding 2817 to our lake and keep exploring
Adding 2918 to our lake and keep exploring
Adding 2919 to our lake and keep exploring
Adding 3019 to our lake and keep exploring
Adding 3120 to our lake and keep exploring
Adding 3220 to our lake and keep exploring
We left the map via 1704 1603 1504 1505 1506 1606 1707 1807 1808 1909 2009 2010 2110 2210 2211 2212 2313 2413 2514 2614 2715 2716 2816 2817 2918 2919 3019 3120 3220
Arrows for 1704 should now point to 1603
Arrows for 1603 should now point to 1504

The key is the list of candidates after looking at the first round of neighbors: 1804 1603 1803 1703 1705 1604. These are sorted by height, 16.04 is last, which is great. But it also means that 16.03 (wrong direction: east into the mountains) is the equivalent of 17.05 and 18.04 (south east, towards lake 18.05).

Clearly, I needed a better sorting algorithm for the next candidates to look for: the candidates at the same level needed to be sorted by their lowest neighbor which had not already been looked at.

A bit later:

Lake started with 1704
Candidates: 1704
Looking at candidate 1704
River now: 1704
A neighbor of 1704 is 1803 with target 1704
Adding 1803 to our lake because it empties into our lake; the river leading here: 1704 1803
A neighbor of 1704 is 1804 with target 1704
Adding 1804 to our lake because it empties into our lake; the river leading here: 1704 1804
A neighbor of 1704 is 1705 with target 1704
Adding 1705 to our lake because it empties into our lake; the river leading here: 1704 1705
A neighbor of 1704 is 1703 with target 1704
Adding 1703 to our lake because it empties into our lake; the river leading here: 1704 1703
A neighbor of 1704 is 1603 with target 1704
Adding 1603 to our lake because it empties into our lake; the river leading here: 1704 1603
A neighbor of 1704 is 1604 with target 1704
Adding 1604 to our lake because it empties into our lake; the river leading here: 1704 1604
lowest neighbor of 1703 is 1702
lowest neighbor of 1703 is 1702
lowest neighbor of 1703 is 1702
Candidates: 1804 1705 1703 1803 1603 1604
Looking at candidate 1804
River now: 1704 1804
Adding lake 1805 to our candidates: 1705 1703 1803 1603 1604 1805
A neighbor of 1804 is 1905 with target 1805
Adding 1905 to our lake because it empties into our lake; the river leading here: 1704 1804 1905
A neighbor of 1804 is 1904 with target 1803
Adding 1904 to our lake because it empties into our lake; the river leading here: 1704 1804 1904
lowest neighbor of 1703 is 1702
lowest neighbor of 1703 is 1702
lowest neighbor of 1703 is 1702
Candidates: 1805 1705 1703 1905 1803 1603 1604 1904
Looking at candidate 1705
River now: 1704 1705
A neighbor of 1705 is 1706 with target 1707
Adding 1706 and 1707 to our lake, but need to explore
Adding 1807 to our lake and keep exploring
Adding 1808 to our lake and keep exploring
Adding 1909 to our lake and keep exploring
Adding 2009 to our lake and keep exploring
Adding 2010 to our lake and keep exploring
Adding 2110 to our lake and keep exploring
Adding 2210 to our lake and keep exploring
Adding 2211 to our lake and keep exploring
Adding 2212 to our lake and keep exploring
Adding 2313 to our lake and keep exploring
Adding 2413 to our lake and keep exploring
Adding 2514 to our lake and keep exploring
Adding 2614 to our lake and keep exploring
Adding 2715 to our lake and keep exploring
Adding 2815 to our lake and keep exploring
Adding 2816 to our lake and keep exploring
Adding 2917 to our lake and keep exploring
Adding 2918 to our lake and keep exploring
Adding 2919 to our lake and keep exploring
Adding 3019 to our lake and keep exploring
Adding 3120 to our lake and keep exploring
Adding 3220 to our lake and keep exploring
We left the map via 1704 1705 1706 1707 1807 1808 1909 2009 2010 2110 2210 2211 2212 2313 2413 2514 2614 2715 2815 2816 2917 2918 2919 3019 3120 3220
Arrows for 1704 should now point to 1705
Arrows for 1705 should now point to 1706

1804 1705 1703 1803 1603 1604 is the correct sorting order. 18.04 and 17.05 are at the front and the southern passage is discovered.

Notice my additional debug message I needed to check that for hex 17.03 the neighbor 17.04 is not checked. It would be the lowest neighbor indeed but we have already looked at it, so we need to ignore it. By the time we’re looking at 17.03, only the three northern hexes are options and 17.02 is indeed the lowest neighbor.

So now, since 18.04 and 17.05 are equally likely, the algorithm picked 17.05 and lake 17.04 doesn’t flow into lake 18.05 but their rivers meet in 17.06 and I think the map is much better, now. Here’s the corrected water flow map:

Correcting the flow of water

And with this correction, the final map also looks different. No more canyon into the mountains!

The new map

Now I get the urge to draw lines indicating the water divides. Noooo! :)

And just in case I make further changes to the algorithm, here’s what the application would generate right now for this width, height, and seed.

Time passes and I’ve spotted another problem in the map above. Take a look at lake 34.05 which drains through one of the highest peaks in the area, 35.07. How on earth did that happen?

We need to look at the earlier maps again. Before the flood processor kicks in we can already see that any water on the peak 35.07 will immediately drain into lake 37.09.

So, the algorithm goes:

  1. look at all the neighbors of 34.05. None look very exciting, they all drain back into 34.05.
  2. look at our neighbors’ neighbors; ignore the two white fields, focus on the light gray ones, e.g. 34.06.
  3. all its neighbors drain back into the lake with the exception of our peak, 35.07; and with that, we found our lake 37.09
  4. looking at all our candidates, lake 37.09 looks like the most promising, since it lies lower than any of the candidates

And that explains it. The problem seems to be that once we are looking at a candidate like 34.06, we’ll evaluate all its neighbors, even the very high ones like 37.09, even if a candidate of similar height like 35.05 has a much better neighbor like 36.05 which would drain off to the north west.

My algorithm is not enough breadth first like water would be.

More tinkering ahead, I can feel it!

Tags:

Comments on 2016-08-16 Alpine Map Generator

Algorithm changed to be totally breadth first. I’m not sure it is better. :)

– Alex Schroeder 2016-08-17 13:24 UTC

Add Comment

2016-08-06 Alpine Map Generator

I’ve been playing around with Text Mapper again. The nice thing about it is that it’s easier to generate the text input for Text Mapper than it is to write a whole new program.

What I wanted was to generate maps that remind of old drawings of the alps, like the one of the Wallis: Vallesia superior, ac inferior, Wallis, le Valais, by Gabriel Walser from 1768. There, you can download a 6208×5151 pixel image.

Anyway, I’ve worked on this for two days and here we are!

If you follow the link, you’ll get a random 20x10 hexmap using the new map generator, with the old SVG framework, and using the same old map icons by +Greg MacKenzie.

The source of your map is in the SVG file, as a comment. Alternatively, use the following link to generate a map description and hit the submit button to render it. If you like it, go back and save the map description:

There are still some things I think are annoying which is why I’ve left the “height” labels in as I need to think about this some more.

  • The river being painted over settlements looks bad, it should perhaps go between the hex background color (such as green) and the icons (i.e. below the forest and below the thorp). I’m not sure how good this looks if I’m using a road instead of a river but there you go. (Fixed!)
  • There are a few more icons I think of using. What about large towns, cities, shrines? No bushes, I think. Where do single trees come in? Where to place a keep? What about using more colors, ocre and sienna maybe? The complete list of icons and colors can be seen in the Gnomeyland example.
  • Also, anybody spotting stuff that defies explanation? Sometimes a river will drain to one side of a ridge – sometimes even appear to flow along side a lowland before turning away – and that is annoying. Perhaps these are opportunities to generate gorges?
  • I think there needs to be a more deliberate spacing of the settlements. “Random” results in random clustering which makes no sense, I think. (Fixed!)

Anybody interested in talking about the algorithm I used? I didn’t put it into words but I might if you want to talk about it and prefer not to see any Perl code… And I definitely need to add a link to this somewhere in the web app. :)

Updates

  1. trails now connect settlements, if within two hexes, three at most
  2. cliffs now separate higher altitudes from lower altitudes if the altitude difference is 2 or more

I’m hoping that cliffs make waterfalls obvious.

I still keep thinking about a way to add shadows and lighting like Swiss Topographical maps have it. Take a look at this nearby hill. Note how subtle shadows indicate altitude changes without you having to read the altitude lines.

Now compare it with this prototype:

Text Mapper with Shadows

I don’t think I like it. Light is basically assumed to come from the east, and if the neighboring hexes are higher up or further down, a little shadow is cast. And yet, something is off. The entire thing is too busy. Are the shadows too deep? Is the problem that these are polygons instead of fuzzy shadows? I tried using two different shades of light and dark so that it would look more organic but it didn’t help. Is it because we can skim over multiple hexes with out eyes and thus if A < B < C then it is not enough that C cast a shadow on B and B casts a shadow on A, do we expect that all of B lies in shadows?

I guess I like the look of the lakes.

Tags:

Comments on 2016-08-06 Alpine Map Generator

I’ve been working for many hours every day, working on better river flows, or basically better flooding of lakes and rivers cutting through higher surrounding areas searching for a passage, creating canyons as they cut through the land.

– Alex Schroeder 2016-08-15 21:29 UTC

Add Comment

2016-08-04 Text Mapper Update

Today I rewrote Text Mapper to be a Mojolicious application. I hope the important URLs still work exactly as they used to, so if you’re using Text Mapper as part of an application of yours, you should be fine. If not, let me know and I’ll see what I can do about it.

Another big step forward are the examples on the Text Mapper Help page. You can click on the links to see the examples, live. This makes me very happy.

These days, I use Text Mapper to maintain the player’s map for my Greyheim campaign. You can see it on the Greyheim Region page.

The reason I rewrote the application was that I actually wanted to write a new algorithm to generate maps – maps that would look like Swiss maps: rivers, ranges of hills and mountains, passes, all of that.

Tags:

Add Comment

2016-04-20 How To Get Started With Text Mapper

Here’s how to get started with a random map:

  1. Visit https://campaignwiki.org/text-mapper
  2. Click the Random button
  3. Click the Submit button
  4. Use the Back button to make changes

(If you click the Random link then you won’t be able to get back to the text that generated it all – you’ll have to look at the page source and at the very end you’ll find the text that generated the map in a comment, just in case.)

To get a feel for the icons available, do the following in another tab:

  1. Visit https://campaignwiki.org/text-mapper
  2. Click Help
  3. Scroll down to the Gnomeyland section
  4. Compare example data and result

Campaign Wiki Bonus: If you can find a way to store your text describing the map online, then your map will be live – you can change the text and reload your map, and it will have changed as well. For one of my campaigns, for example, I keep the text on on a page, and I use the link to the raw text of that page to generate the actual map.

Dropbox: If you store your map in a public (!) text file you can edit the map and regenerate the map on demand. Here’s the text file for my current map.

Awesome! :)

Tags:

Add Comment

More...

Comments


Please make sure you contribute only your own work, or work licensed under the GNU Free Documentation License. Note: in order to facilitate peer review and fight vandalism, we will store your IP number for a number of days. See Privacy Policy for more information. See Info for text formatting rules. You can edit the comment page if you need to fix typos. You can subscribe to new comments by email without leaving a comment.

To save this page you must answer this question:

Please say HELLO.