﻿<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text" xml:lang="en">Javascript</title>
  <link type="application/atom+xml" href="https://d.moonfire.us/tags/javascript/atom.xml" rel="self" />
  <link type="text/html" href="https://d.moonfire.us/tags/javascript/" rel="alternate" />
  <updated>2026-09-06T17:41:56Z</updated>
  <id>https://d.moonfire.us/tags/javascript/</id>
  <author>
    <name>D. Moonfire</name>
  </author>
  <rights>Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</rights>
  <entry>
    <title>Finishing up this round of MfGames Culture</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2015/12/06/finished-up-culture/" />
    <updated>2015-12-06T06:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2015/12/06/finished-up-culture/</id>
    <category term="programming" scheme="https://d.moonfire.us/categories/" label="Programming" />
    <category term="author-intrusion" scheme="https://d.moonfire.us/tags/" label="Author Intrusion" />
    <category term="flight-of-the-scions" scheme="https://d.moonfire.us/tags/" label="Flight of the Scions" />
    <category term="javascript" scheme="https://d.moonfire.us/tags/" label="Javascript" />
    <category term="mfgames-culture" scheme="https://d.moonfire.us/tags/" label="MfGames Culture" />
    <category term="nodejs" scheme="https://d.moonfire.us/tags/" label="NodeJS" />
    <summary type="html">Retrospective on a month of working on MfGames Culture.</summary>
    <content type="html">&lt;p&gt;For November, I decided to focus on &lt;a href="https://mfgames.com/mfgames-culture/"&gt;MfGames Culture&lt;/a&gt;. It was a pretty productive month in many ways, but the development cycle has ended and it is a good time to report where I am and what needs to happen next.&lt;/p&gt;
&lt;h1&gt;Reasons&lt;/h1&gt;
&lt;p&gt;I've been trying to write this library for a number of years. I've tried it in a few forms, the C# version being the previously most successful one, but it hit some conceptual snags and overwhelming details (the bulk of the reasons why my bigger projects fail).&lt;/p&gt;
&lt;p&gt;This time, I tried something entirely different, I wrote it in Javascript (well, Typescript). Typescript is &lt;em&gt;not&lt;/em&gt; one of my primary languages, but I think it has a good chance of becoming one. I've played with it in a number of other iterations, mostly related to &lt;a href="/tags/author-intrusion/"&gt;Author Intrusion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The library is a small but rather complex part of my master plan for &lt;em&gt;Author Intrusion&lt;/em&gt;. I want to use this library to tag the headers of my various chapters and tell me when and where it happens. The library is also to let me build a timeline of the entire world of &lt;a href="https://fedran.com/"&gt;Fedran&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Confusion&lt;/h1&gt;
&lt;p&gt;The biggest problem with Typescript is that I couldn't figure out the &lt;em&gt;One True Way&lt;/em&gt; of getting it to work. Whenever I thought I figure out how to get something working, it broke with the next step. And then I'd puzzle through that and come up with a new way, it ended up breaking up with the next one. It was a rather frustrating experience, to say the least.&lt;/p&gt;
&lt;p&gt;My first thought was to write using ES6 modules (&lt;code&gt;tsc --target es6&lt;/code&gt;). I figured that Author Intrusion is going to take five years, so going with the edge development seemed like the right idea.&lt;/p&gt;
&lt;p&gt;Needless to say, I &lt;em&gt;love&lt;/em&gt; the ES6 import syntax. It compiled great and everything was perfect.&lt;/p&gt;
&lt;p&gt;At least until I needed to get it tested. I really like to use TDD (Test-Driven Development) which means I usually write with tests to help prove out small, discrete concepts. I do not do BDD or top-level testing, but working on internal logic to make sure the foundation was correct.&lt;/p&gt;
&lt;p&gt;To get testing working, I ended up having to go to &lt;code&gt;jasmine-es6&lt;/code&gt; but after trying &lt;code&gt;mocha&lt;/code&gt;, &lt;code&gt;jasmine&lt;/code&gt;, and a bunch of other things.&lt;/p&gt;
&lt;p&gt;Eventually, I got &lt;a href="https://github.com/dmoonfire/mfgames-culture-js/"&gt;mfgames-culture-js&lt;/a&gt; working (well, it was called something else at the time).&lt;/p&gt;
&lt;p&gt;Then I encountered the next stumbling block. I tried to put it up as a &lt;a href="https://mfgames.com/mfgames-culture/fiddle"&gt;fiddle&lt;/a&gt;. This was a fun experience, mainly because I have not really made a web-based Javascript page before, but also because my nice and stable system crumbled. Absolutely failed because I couldn't figure out how to get the ES6 module to work with the website.&lt;/p&gt;
&lt;p&gt;I tried a lot of things, &lt;code&gt;babel&lt;/code&gt; and &lt;code&gt;webpack&lt;/code&gt;, but I couldn't figure out them out. There were errors messages that apparently &lt;em&gt;no one else in the universe had ever had&lt;/em&gt;. I spent over a week trying to get it working. I also tried creating two versions of the &lt;code&gt;mfgames-culture-js&lt;/code&gt; (AMD and CommonJS) to use that.&lt;/p&gt;
&lt;p&gt;Eventually, I went back to &lt;code&gt;mfgames-culture-js&lt;/code&gt; and redid it as UMD (universal module definition). That required me to rework the testing framework, but I could go back to the regular &lt;code&gt;jasmine&lt;/code&gt; which I was happy about. I did have to reduce the entire project to one file (as opposed to breaking it apart) because Typescript and RequireJS don't seem to support multiple files being reexported or combined together. &lt;em&gt;I really hate having a 1,000+ line source file.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Following the refactoring that came with it, I finally got my website working. I figured I was free and clear.&lt;/p&gt;
&lt;p&gt;And then I started working on the command-line client (&lt;code&gt;mfgames-culture-cli&lt;/code&gt;). So, the CLI is based on NodeJS and CommonJS modules while the website used AMD modules. I had to split my JS library in two because it was the only way I could figure things out.&lt;/p&gt;
&lt;p&gt;Another week later, I managed to get everything working smoothly in a Frakenstein-like system. I don't think it is the &lt;em&gt;right&lt;/em&gt; answer, but it worked for me. At least until I try to do something else, that is.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UMD modules for the core libraries&lt;/li&gt;
&lt;li&gt;Jasmine for testing&lt;/li&gt;
&lt;li&gt;NPM for various scripts (as opposed to &lt;code&gt;gulp&lt;/code&gt; or &lt;code&gt;grunt&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;babel&lt;/code&gt; or &lt;code&gt;webpack&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also ended up with four packages and one website:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mfgames-culture-data&lt;/code&gt;: JSON data files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames-culture-js&lt;/code&gt;: Typescript/Javascript core module.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames-culture-node&lt;/code&gt;: NodeJS-specific modules to support the main one.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames-culture-cli&lt;/code&gt;: Command-line interface to use it.&lt;/li&gt;
&lt;li&gt;My fiddle website&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also spent another week trying to figure out how to get Typescript &amp;ldquo;typings&amp;rdquo; working. That still hasn't been resolved yet, but I ran out of time. I'll puzzle that out the next round, mainly to &amp;ldquo;finish up&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I also spent a week documenting it and I feel that I'm only 50% done with that task.&lt;/p&gt;
&lt;h1&gt;Complexity&lt;/h1&gt;
&lt;p&gt;Calendars are complex. This weekend, I tried to explain to my dad how my system works. Going through the JSON files, I realized that this really isn't a trivial system and I don't think I can make it simple. I can make it easy to isolate and work, but this will never be &amp;ldquo;easy&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I did create a &lt;a href="http://discuss.moonfire.us/c/mfgames/mfgames-culture"&gt;forum&lt;/a&gt; to talk about it. I'll be glad to help if anyone does decide to join in or try to use it.&lt;/p&gt;
&lt;h1&gt;Retrospective&lt;/h1&gt;
&lt;p&gt;I spent a &lt;em&gt;lot&lt;/em&gt; of time trying to figure things out. All my web searches didn't really give a good answer, so I fumbled through it. I may have gotten a decent work flow, but it doesn't feel &amp;ldquo;right&amp;rdquo; yet.&lt;/p&gt;
&lt;p&gt;I also uploaded everything to npmjs.com, so anyone can use them.&lt;/p&gt;
&lt;h1&gt;Next for the library&lt;/h1&gt;
&lt;p&gt;There are a few steps still left for the library:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get my Fedran calendars implemented&lt;/li&gt;
&lt;li&gt;Get the Aztec/Mayan calendar working&lt;/li&gt;
&lt;li&gt;Finish documentation&lt;/li&gt;
&lt;li&gt;Get various typings submitted so others can use it&lt;/li&gt;
&lt;li&gt;Looking into the &lt;code&gt;typings&lt;/code&gt; infrastructure&lt;/li&gt;
&lt;li&gt;Get Typescript to reference modules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm sure as my understanding and the ecosystem both evolve, I'll change how to these systems are arrange. That is why I kept them at the 0.1.0 level. I am nowhere close to being version 1.0.0.&lt;/p&gt;
&lt;h1&gt;December plans&lt;/h1&gt;
&lt;p&gt;The main reason I worked on this library for November was to take a break from writing. I posted on Facebook and a few other places, but generally I was frustrated with my weekly serials. I made a decision to start releasing &lt;a href="/tags/flight-of-the-scions/"&gt;Flight of the Scions&lt;/a&gt; for &lt;a href="https://www.patreon.com/dmoonfire?ty=h"&gt;Patreon&lt;/a&gt; subscribers only. I'll do another blog post on that, but I'm getting excited about writing again.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Creating slippery maps</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2014/09/13/making-slippery-maps/" />
    <updated>2014-09-13T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2014/09/13/making-slippery-maps/</id>
    <category term="artistic" scheme="https://d.moonfire.us/categories/" label="Artistic" />
    <category term="programming" scheme="https://d.moonfire.us/categories/" label="Programming" />
    <category term="exalted" scheme="https://d.moonfire.us/tags/" label="Exalted" />
    <category term="javascript" scheme="https://d.moonfire.us/tags/" label="Javascript" />
    <category term="openlayers" scheme="https://d.moonfire.us/tags/" label="OpenLayers" />
    <category term="slippery-map" scheme="https://d.moonfire.us/tags/" label="Slippery Map" />
    <category term="tutorial" scheme="https://d.moonfire.us/tags/" label="Tutorial" />
    <summary type="html">Someone recently asked me how to create an online map of a created world. I decided to use this opportunity to update my Exalted online map to a newer library while writing a tutorials.</summary>
    <content type="html">&lt;p&gt;Recently, I &lt;a href="http://www.reddit.com/r/worldbuilding/comments/2g3u7y/is_there_a_program_with_an_interactive_map_style/ckfdza1"&gt;answered a question&lt;/a&gt; about creating an online map of someone's fantasy world. I mentioned &lt;a href="http://wiki.openstreetmap.org/wiki/Slippy_Map"&gt;slippery maps&lt;/a&gt; which is the generic name for maps like Google Maps or MapQuest where you can drag around the map, zoom into it, and maybe set up bookmarks.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you just want to look at the results, head over &lt;a href="http://exalted-map.mfgames.com/"&gt;to the website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, a &lt;em&gt;lot&lt;/em&gt; of things changed since 2006 when I last created a map. Then, I needed to create some PHP code to manage it and spent better part of a week trying to jam Google Map's API into something that would work.&lt;/p&gt;
&lt;p&gt;Today, things are a lot different and a lot easier to create something that looks good. It also doesn't require a PHP host or a database to run. But, to answer the question I gave in more detail, this is a short tutorials on how to create a slippery map of a fantasy world.&lt;/p&gt;
&lt;h1&gt;Zoom Levels and Tiles&lt;/h1&gt;
&lt;p&gt;Probably the best way to understand slippery maps is to understand zoom levels. The lowest detail, highest area is zoom zero (Z0). This represents the entire world as a single 256 pixel square tile.&lt;/p&gt;
&lt;p&gt;A zoom level starts with zero (Z0) which represents the entire world in a single tile.&lt;/p&gt;
&lt;img class="img-responsive" src="x0y0.png" /&gt;
&lt;p&gt;Each &lt;em&gt;zoom&lt;/em&gt; level higher doubles the image size. So Z1 is a 512 pixel square image and Z2 is a 1,024 pixel square image. Google Maps allows up to Z18 which is an image 67,108,864 pixels on a side. At that level, we can see the driveway leading into most houses but the image requires at least 16 petabytes to load into memory at once.&lt;/p&gt;
&lt;p&gt;I don't about you, but I'd love to have a 16 PiB RAM machine, but that isn't going to happen. Not to mention, you'd have to download all 16 PiB to actually see it.&lt;/p&gt;
&lt;p&gt;So, to break it into smaller parts that are easier to download, display, and manage, slippery maps use 256 pixel square tiles. Each of those images are broken down into these consistent-sized tiles which are then downloaded as the library needs it.&lt;/p&gt;
&lt;p&gt;Since we aren't using a full image, we have to know &lt;em&gt;where&lt;/em&gt; in the bigger image we need to retrieve these images. And this introduces the coordinate system for slipper maps. The basic coordinate is a (z, x, y) where &lt;em&gt;z&lt;/em&gt; is the zoom level. Both &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; are the zero-based index from the upper-left of the image.&lt;/p&gt;
&lt;p&gt;Z0 would only have a single image (0, 0, 0). Z1 would have four: (1, 0, 0), (1, 0, 1), (1, 1, 0), and (1, 1, 1). Z2 would have sixteen and so on.&lt;/p&gt;
&lt;p&gt;When a slippery map needs to retrieve the image, it maps the coordinate system into a URL of some sort. For example: &lt;a href="http://exalted-map.mfgames.com/creation/z0/x0y0.png"&gt;http://exalted-map.mfgames.com/creation/z0/x0y0.png&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The final component of a slippery map is a &lt;em&gt;layer&lt;/em&gt;. This can be raster data, such as satellite or a political map. It can also be vector data, such as a map of all streets in a tile. In the above example, the layer is actually &lt;code&gt;creation&lt;/code&gt;. There is also &lt;code&gt;creation-geo&lt;/code&gt; which is a second raster layer that doesn't have cities, geomancy, or boundaries on it.&lt;/p&gt;
&lt;p&gt;The only limitation on how far you allow zooming or which layers is time. It does take a fair amount of time to create and manage tiles. The higher the acceptable zoom level, the more work and disk space is required to keep those tiles and to make them look good.&lt;/p&gt;
&lt;h1&gt;Starting points&lt;/h1&gt;
&lt;p&gt;Because the Javascript can't create the tiles for me, and I'm not using PHP, I need to pre-render the individual tiles at all the zoom levels I need which will then be uploaded to my website.&lt;/p&gt;
&lt;p&gt;For me, the best place to start is with a relatively large map in &lt;a href="http://en.wikipedia.org/wiki/Mercator_projection"&gt;Mercator project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I'm not going to use a proper Mercator projected map since I'm going to put my &lt;a href="https://mfgames.com/exalted-map/"&gt;Exalted Map of Creation&lt;/a&gt; back online and I already had most of the work done. This is a flat map without projection, so most of the coordinates are hacked to get things to work.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I decided to focus on a zoom level five for the map which requires me to start with a 8,192 pixel square. Fortunately, SVG scales very well so I have a relatively sharp image at that level.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What doesn't work is that my map of Creation isn't square. I'm cheating and just putting black bars on the top and bottom, but I highly recommend you use a properly projected map that fills the tile completely.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I do &lt;a href="http://exalted-map.mfgames.com/creation/map-z5.png"&gt;host a full version of the map&lt;/a&gt; on my website (there is also z0 through z4, there is also creation-geo for maps without markers). Feel free to download or use that, or even hotlink. It's there until I run out of bandwidth or something goes wrong.&lt;/p&gt;
&lt;p&gt;The next step is to create 256 pixel square tiles from the image and give them a consistent name. Now, the tedious approach is to manually slice out each one and save it to the disk. For the larger map, that can take hours.&lt;/p&gt;
&lt;p&gt;I happen to be lazy, I don't do tedious things like this when I can find a tool to do it for me. Fortunately &lt;a href="http://www.imagemagick.org/"&gt;ImageMagick&lt;/a&gt; has all the tools I need. This runs under Windows (you have to double up on the &lt;code&gt;%&lt;/code&gt; though for the DOS command prompt) also, for those who don't use Linux.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ convert map-z5.png -crop 256x256 -set filename:tile &amp;quot;x%[fx:page.x/256]y%[fx:page.y/256]&amp;quot; +repage +adjoin &amp;quot;z5/%[filename:tile].png&amp;quot;
$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This basically creates 1024 tile images in &lt;code&gt;exalted-map/z5&lt;/code&gt; where the name is &lt;code&gt;xMyN.png&lt;/code&gt; where &lt;em&gt;M&lt;/em&gt; and &lt;em&gt;N&lt;/em&gt; are numbers from 0 to 31. This means that you can get a tile just from the middle by using something like &lt;code&gt;exalted-map/z5/x15y15.png&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, if you can't guess from the above path, I'm putting all the zoom levels above five (four through zero) in the same structure.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ convert -scale 50% map-z5.png map-z4.png
$ convert map-z4.png -crop 256x256 -set filename:tile &amp;quot;x%[fx:page.x/256]y%[fx:page.y/256]&amp;quot; +repage +adjoin &amp;quot;z4/%[filename:tile].png&amp;quot;
$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is all in a &lt;a href="http://exalted-map.mfgames.com/creation/Makefile"&gt;makefile&lt;/a&gt; so I can just type &lt;code&gt;make&lt;/code&gt; and have it generate all six zoom levels in a single command. Now, if you have a custom map for a different zoom level, then don't scale it from the higher level (z5 to z4). I'm only going to have the one for each layer, so I'm just scaling the entire thing from Z5.&lt;/p&gt;
&lt;h1&gt;Putting it up&lt;/h1&gt;
&lt;p&gt;Now that I have the tiles at all the zoom levels (Z0 through Z5) generated, I need to put them somewhere that can be accessed via a HTTP request. If you can't guess, I have a host at &lt;a href="http://exalted-map.mfgames.com/"&gt;http://exalted-map.mfgames.com/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The tiles have to have a consistent name for the slippery map to work. I decided to use the pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://exalted-map.mfgames.com/{layer}/z{zoom}/x{x}y{y}.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the moment, I'm allow providing two layers: &lt;code&gt;creation&lt;/code&gt; and &lt;code&gt;creation-geo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The above URL is important for when we hook up a library to our URL, but the important part is that you can go to any image on that map and have it produce the correct results.&lt;/p&gt;
&lt;h1&gt;Creating the HTML&lt;/h1&gt;
&lt;p&gt;I decided to use &lt;a href="http://openlayers.org/"&gt;OpenLayers 3&lt;/a&gt; as my Javascript library. This is a pretty solid library, though there are others that can do much of the same functionality.&lt;/p&gt;
&lt;p&gt;On the &lt;a href="http://openlayers.org/en/v3.0.0/doc/quickstart.html"&gt;quickstart&lt;/a&gt; directions, they have a very basic HTML page that uses OpenLayers 3 and has the basic page. Take that HTML file and throw it up on a website (maybe at the &lt;a href="http://exalted-map.mfgames.com/"&gt;root above the layers&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;If you load the page, you should see MapQuest map. But we don't really want to see that, we want to see our own map. To do that, we change the layer:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;layers: [
  new ol.layer.Tile({
    source: new ol.source.XYZ({
      layer: 'creation-geo',
      url: 'http://exalted-map.mfgames.com/creation-geo/z{z}/x{x}y{y}.png'
    })
  })
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refresh the page and you have a basic slippery map.&lt;/p&gt;
&lt;h1&gt;Icons and Locations&lt;/h1&gt;
&lt;p&gt;Getting locations to show up in the map is a &lt;em&gt;bit&lt;/em&gt; harder. In OpenLayers 3, there are a lot of ways of doing it and I haven't quite found a perfect way. But, a &amp;ldquo;good enough&amp;rdquo; approach uses KML and a bit of magic.&lt;/p&gt;
&lt;p&gt;This is the basic KML file. It just lists points on a map along with their icons. If you follow the &lt;a href="http://exalted-map.mfgames.com/icons/"&gt;icon links&lt;/a&gt;, you can find the icon images, they aren't that impressive (though I did make them in Inkscape).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;kml&amp;gt;
  &amp;lt;Document&amp;gt;
	&amp;lt;name&amp;gt;Exalted Canon Locations&amp;lt;/name&amp;gt;
    &amp;lt;description&amp;gt;Created from stephenls map data.&amp;lt;/description&amp;gt;
    &amp;lt;Folder&amp;gt;
      &amp;lt;name&amp;gt;Locations&amp;lt;/name&amp;gt;
      &amp;lt;description&amp;gt;Locations&amp;lt;/description&amp;gt;

      &amp;lt;Style id=&amp;quot;style-canon-gate&amp;quot;&amp;gt;
        &amp;lt;IconStyle&amp;gt;
          &amp;lt;scale&amp;gt;1.0&amp;lt;/scale&amp;gt;
          &amp;lt;Icon&amp;gt;
            &amp;lt;href&amp;gt;http://exalted-map.mfgames.com/icons/canon-gate.png&amp;lt;/href&amp;gt;
          &amp;lt;/Icon&amp;gt;
        &amp;lt;/IconStyle&amp;gt;
      &amp;lt;/Style&amp;gt;
      &amp;lt;Style id=&amp;quot;style-canon&amp;quot;&amp;gt;
        &amp;lt;IconStyle&amp;gt;
          &amp;lt;scale&amp;gt;1.0&amp;lt;/scale&amp;gt;
          &amp;lt;Icon&amp;gt;
            &amp;lt;href&amp;gt;http://exalted-map.mfgames.com/icons/canon.png&amp;lt;/href&amp;gt;
          &amp;lt;/Icon&amp;gt;
        &amp;lt;/IconStyle&amp;gt;
      &amp;lt;/Style&amp;gt;

      &amp;lt;Folder&amp;gt;
        &amp;lt;name&amp;gt;POI&amp;lt;/name&amp;gt;
        &amp;lt;Placemark&amp;gt;
		  &amp;lt;name&amp;gt;Imperial Mountain&amp;lt;/name&amp;gt;
          &amp;lt;styleUrl&amp;gt;#style-canon&amp;lt;/styleUrl&amp;gt;
          &amp;lt;Point&amp;gt;
            &amp;lt;coordinates&amp;gt;-20, 12&amp;lt;/coordinates&amp;gt;
          &amp;lt;/Point&amp;gt;
        &amp;lt;/Placemark&amp;gt;
	  &amp;lt;/Folder&amp;gt;

	  &amp;lt;Folder&amp;gt;
		&amp;lt;name&amp;gt;Gates&amp;lt;/name&amp;gt;
		&amp;lt;description&amp;gt;Celestial Gates&amp;lt;/description&amp;gt;
        &amp;lt;Placemark&amp;gt;
		  &amp;lt;name&amp;gt;Celestial Gate #10&amp;lt;/name&amp;gt;
          &amp;lt;styleUrl&amp;gt;#style-canon-gate&amp;lt;/styleUrl&amp;gt;
          &amp;lt;Point&amp;gt;
            &amp;lt;coordinates&amp;gt;22.7673, 8&amp;lt;/coordinates&amp;gt;
          &amp;lt;/Point&amp;gt;
        &amp;lt;/Placemark&amp;gt;
	  &amp;lt;/Folder&amp;gt;
	&amp;lt;/Folder&amp;gt;
  &amp;lt;/Document&amp;gt;
&amp;lt;/kml&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I uploaded the KML file to the site under &lt;a href="http://exalted-map.mfgames.com/canon.kml"&gt;http://exalted-map.mfgames.com/canon.kml&lt;/a&gt;. If you can't guess, the library needs to download it to use it.&lt;/p&gt;
&lt;p&gt;Adding the KML file to the map is pretty easy, you do it as a second layer.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;var creationGeography = new ol.layer.Tile({
    source: new ol.source.XYZ({
        // attributes: Always give credits
        url: &amp;quot;http://exalted-map.mfgames.com/creation-geo/z{z}/x{x}y{y}.png&amp;quot;,
    }),
});

var canonLocations = new ol.layer.Vector({
    source: new ol.source.KML({
        projection: &amp;quot;EPSG:3857&amp;quot;,
        url: &amp;quot;canon.kml&amp;quot;,
    }),
});

var map = new ol.Map({
    target: &amp;quot;map&amp;quot;,
    layers: [creationGeography, canonLocations],
    view: new ol.View({
        center: [0, 0],
        zoom: 3,
    }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll notice I refactored the geography stuff out into a variable to make it easier to read. I do that a lot as I'm working and puzzling through things.&lt;/p&gt;
&lt;p&gt;When you upload and refresh, you'll see pretty icons for all of the canon locations. The reason I'm using &amp;lsquo;canon&amp;rsquo; is because I had a secondary layer called &amp;lsquo;sepia&amp;rsquo; for my Sepia Throne campaign. We had a tendency to create new locations, destroy cities, and we also used 108 celestial gates instead of the 56 in the canon world. (The 108 came from the 108 Stars of Destiny.)&lt;/p&gt;
&lt;h1&gt;Clickable labels&lt;/h1&gt;
&lt;p&gt;And the final part. It isn't obvious from the icons which gate it is, so it would be nice if you could click on it and get a bit more information. To do that, we are going to show a bootstrap popup of the KML name, which should be enough.&lt;/p&gt;
&lt;p&gt;This needs three parts. The first is to add Bootstrap and jQuery to your webpage. View source at the map to find examples if you don't know how.&lt;/p&gt;
&lt;p&gt;The second is to add a popup element in the map.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;div id=&amp;quot;map&amp;quot; class=&amp;quot;map&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;popup&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, a bit more Javascript.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;var element = document.getElementById(&amp;quot;popup&amp;quot;);

var popup = new ol.Overlay({
    element: element,
    positioning: &amp;quot;bottom-center&amp;quot;,
    stopEvent: false,
});

map.addOverlay(popup);

// display popup on click
map.on(&amp;quot;click&amp;quot;, function (evt) {
    var feature = map.forEachFeatureAtPixel(
        evt.pixel,
        function (feature, layer) {
            return feature;
        }
    );

    if (feature) {
        var geometry = feature.getGeometry();
        var coord = geometry.getCoordinates();
        popup.setPosition(coord);

        $(element).popover(&amp;quot;destroy&amp;quot;);
        $(element).popover({
            placement: &amp;quot;auto&amp;quot;,
            html: true,
            content: feature.get(&amp;quot;name&amp;quot;).replace(/ /g, &amp;quot;&amp;amp;#160;&amp;quot;),
        });
        $(element).popover(&amp;quot;show&amp;quot;);
    } else {
        $(element).popover(&amp;quot;destroy&amp;quot;);
    }

    feature = null;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Conclusions&lt;/h1&gt;
&lt;p&gt;There are a few more things I'd like to do, but&amp;hellip; it's good enough for now. If you want to see a full version, check out the source of &lt;a href="http://exalted-map.mfgames.com/index.html"&gt;http://exalted-map.mfgames.com/index.html&lt;/a&gt; and &lt;a href="http://exalted-map.mfgames.com/map.js"&gt;http://exalted-map.mfgames.com/map.js&lt;/a&gt;. That should give you a rough idea of how to do it, along with poor comments on what we're doing.&lt;/p&gt;
&lt;p&gt;I don't like using the KML approach, but I couldn't easily figure out how to do it via JSON or creating the features manually. For some reason, &lt;code&gt;ol.geom.Point&lt;/code&gt; wasn't working with a &lt;code&gt;ol.Feature&lt;/code&gt; and I didn't know why. This works for now, but if I was going to add the rest of the points, I'd find a cleaner way of doing it.&lt;/p&gt;
&lt;p&gt;The main reason I don't want to use KML is because I want to have a link to a wiki site of some sort, or maybe some additional text. These are things easily done via JSON but not KML.&lt;/p&gt;
</content>
  </entry>
</feed>
