Custom 3D laser cut maps
trun Visual Art DK30 Fall 2020 2 3
Description
A hybrid art / engineering project that will allow anyone on the internet to design (and purchase) a fully custom, 3D layered street map of any location of their choosing. Users will be able to customize the level of detail, number of layers, type of wood and add personal touches as well. I currently make these maps for fun and mostly give them away to friends as gifts, but they are very time consuming to design by hand and I want to share them with a wider audience.
Recent Updates
Day 28 update: I’ve been stuck pretty hung up how to make the UX for the layer customization intelligible so I haven’t made much progress for the last couple days. In the meantime I took a little creative break to cut some new maps and clean my Glowforge. I haven’t cleaned the optics since I got it and that’s probably close to 80 hours of cut time. Lately I’ve noticed that maps are not cutting cleanly either, which leads to lots of delicate pieces breaking off during assembly. After a thorough wipe down of the optics I’m back in business and cuts are as smooth as ever! Here’s a trail map I made this week just to mix things up a bit.
On the software front I did refactor a good bit of code to prep for the eventual move into a larger web-app that can save and export images. I’m doing the backend in Python / Django just because I’ve done it enough times that it’s pretty straightforward. Hopefully I’ll be able to finish that up before I’m back to work next week.
Day 23 update: After a couple days off, lots more work on the preview today. I managed to get all the relevant preview layers separated and allow their styles to be customized on the fly. Mapbox actually made this relatively easy to do and I’m getting more and more comfortable with their “studio” editor to make these maps even more customizable in the future. Quick preview of what that looks like right now…
Before I do more customization options I need to put some cycles into the backend to make this more manageable to share with folks. The UI is also getting quite busy so I’m thinking I’ll have to split it up somehow - perhaps into distinct steps.
Day 19 update: Lots of improvements to the preview today. I spent a good chunk of the morning wiring up real openstreetmap data that’s queried based on your current map view. However the limitations of that approach became clear extremely quickly…
- I don’t have land polygons made for arbitrary locations (and it would take quite a bit of work to produce those and host them). This means that there’s no clear boundary where the land ends and the ocean begins for any coastal maps.
- Querying for any location with a relatively zoomed out view (e.g. all of Manhattan) routinely timed out or ran into rate limits that prevented data from loading.
- Even when it did work, it was really slow. Toggling back and forth between the map mode and the preview mode was painful.
Before I completely abandoned my OSM backed map, I thought I might be able to work around the lack of land polygons by creating a custom mapbox style that just had land and water…
This turned out to be pretty easy and worked well enough that when it came time to scrap the OSM data completely, I felt pretty comfortable creating a completely custom style to power the whole preview. At that point I added in roads and added a plugin called mapbox-gl-sync-move
to toggle back and forth between the preview and the regular map instantaneously and seamlessly. Here’s the current working map style that I’m playing with…
There’s more tweaking to do to get the map to look right at different zoom levels, but by in large I’m very happy going with a completely mapbox based solution. It does introduce a new problem that I still need to figure out how to go from a set of bounds and options to a Illustrator ready SVG, but I potentially do that all on the backend / offline now so there’s a lot more flexibility.
Next up there’s a few more customization items…
- Add customized text (e.g. the city name) to the map
- Add support for rotation to the map
- Add customization options for the wood types and layers
And then lastly start building out the backend to save designed maps in somewhere other than a URL
Day 18 update: Didn’t get a chance to post last night, but I have a working version deployed!
It doesn’t do a ton yet, but I’m fairly pleased with the basics of the map functionality. The major task to get this working yesterday required me to refactor a good bit of code to orient the map around bounds instead of around a center point. The bounds in this case are the edges of the framed area you’ve selected. So now if you resize your screen or send your map to a friend with a different sized monitor, they’ll actually see the beautifully framed map you just designed rather than some other arbitrary area centered around the same point.
Speaking of sending to a friend, there’s no backend yet, so state is stored in the hash string of the URL. This was a quick and dirty way to make it easy to bookmark a link to a specific area or share with someone else.
Next up is to iterate on the “preview” mode where I actually query for and overlay roads and other geographic elements you pick out.
Day 17 update: I spent the majority of the last two days totally rewriting my leaflet based mapping solution from scratch using Mapbox. One of the big reasons I was using leaflet in the past was for its built-in SVG rendering. However, given the lackluster quality of that renderer in testing earlier this week I had to find a different solution. That solution turned out to be geojson2svg
which worked beautifully. Given map extents and a desired output size it produces a well align SVG, works in-browser, and works very quickly.
With that rendering challenge out of the way, I was free to ditch leaflet in favor of Mapbox. Mapbox offers a number of big improvements…
- It looks beautiful. Leaflet’s tilesets were fine, but they were a little reminiscent of MapQuest circa 1998.
- It’s smooth. The zoom in particular is much smoother than leaflet’s which is stepped at a fixed interval. Having a smoother zoom allows the user to dial in a very precise framing for their image without needing additional controls. Panning performancing is also significantly improved, allowing the “frame” to stay visible throughout the pan rather than snapping back into place once the user is finished dragging.
- It’s fast. Anecdotally Mapbox seems significantly faster than leaflet, even when rendering very large GeoJSON data sets.
The downside is that Mapbox will eventually hit a paid tier, but given that I’m trying to build a revenue generating product that is an acceptable tradeoff for me. I’m still working on getting this deployed, but here are some work-in-progress screenshots of the current functionality…
Customize map size, frame, and corner
Panning and zooming to get the perfect framing
Render GeoJSON layers and export to SVG
On the wood finishing front, I did a little experimentation with sanding and finishing with Shellac yesterday. The first attempt was a little sloppy, but the boards already look much better than they do with no finish at all. Going to get a little pipeline set up later this week to hopefully finish a whole batch so I have some production quality wood to cut again.
Day 15 update: Took a break from working on the software side of things for a few days. Unfortunately, after a bunch of failed attempts, I wasn’t able to make a clean translation from raw GeoJSON features to a format I can render into my 3D preview, so I’ll have to circle back on that particular feature later. Diving back in today to kick off week three…
This week’s goal is to get a fully functional, end-to-end design tool up and running in the browser. There’s a few basic requirements to be able to accomplish that goal:
- Users can pan / zoom to a location of their choosing on the map
- Various geographic features (water, roads, etc) are queried from OSM and overlaid onto the map as distinct “layers”, producing a basic preview for the user.
- This composite, layered view can be exported into an SVG, along with the necessary extent data for me to import into Illustrator, crop it down and then render into a laser ready design.
I salvaged some code I had written back in May to kick-start things with some basic rendering of OSM features through leaflet. Using some hard coded GeoJSON data sets to speed up development, I was able to hack together a rough, framed preview, which can be freely panned / zoomed by the user to adjust their map. Hopefully I can get a live version deployed tomorrow, but here’s a screenshot for now…
Some oddities I ran into while getting this up and running today…
- Getting closed polygons for the land layer is actually incredibly tricky. You can download a giant multi-gigabyte file of all the coastal polygons, but there doesn’t seem to be an easy way to query for this dynamically. I’ll probably have to do split this file up into “tiles” (it is already kind of in this format thankfully) and then serve them up myself depending on what region a user is looking at. I can probably optimize this by searching for
natural=coastline
features within the extent of the current view to avoid fetching these large files when there is not coastline, but I also sort of assume that the most interesting maps are going to be things on the water so this will ultimately be important to get right. - Leaflet’s SVG renderer seems to be doing some very strange downsampling of geometry based on your zoom level. This leads to small objects having extremely low fidelity when they get exported to an SVG directly from Leaflet. Here’s an example of how this absolutely destroys a small pond in Boston’s public garden…
After doing some spelunking in the source code I don’t see any obvious reason why this is happening with the possible exception of floating point precision being capped. My alternate solution is to export all the elements in view back out to GeoJSON where they do seem to retain their original fidelity. Something like this geojson2svg library might be a viable tool.
On the map-making front I made a fun little map of my hometown on Friday. I used some scrap, unfinished wood and mixed up the color scheme, and I actually kind of like the way it turned out. This map definitely underscores the need to use finished wood in anything that I sell though - the unfinished stuff just doesn’t have the same polished look.
I got a large assortment of hardwood delivered today as well, along with some Shellac and a new sander. I’ll probably spend some time testing out finishes on these boards this week because I’m totally out of finished material at this point.
Day[9] update: Spent more time playing with the old maps on the laser cutter today than I did on coding, but I did manage to make some updates to my three.js preview.
- Added separate layers for roads, land and the base (water). This actually turned out to be much easier than I thought it would be. I still need to figure out how to get the engraved elements into the scene though - this preview just has the cuts for the moment.
- Added some basic shadows. These look pretty crappy right now with lots of weird banding, but I just wanted to get something working so I had a starting point to experiment with.
- Changed the rotation to be click and drag rather than simply mirroring the X coordinate of the mouse. I made this rotate the objects rather than the camera somewhat intentionally so you can see the shadows change when the object moves. This has the downside of being a little buggy due to how it’s hooked into react, but it works for now. I’ll look into attaching a light to the camera and moving that instead later.
Sandbox demo: https://codesandbox.io/s/portland-preview-test-fqswv?file=/src/App.js
And the finished map for comparison…
Day 8 update 2: Spent most of the day playing with three.js to learn how to render my map preview into a 3D layer. It isn’t very pretty yet, but it does kinda work. I also added some basic rotation w/ mouse movement (which definitely seems like the kind of thing that you don’t want to do in react, but it’s fine for now). Things left to do for it to be “production ready”…
- Dynamic sizing - to center the object correctly around the axis as it rotates I need to know how big it is.
- Add more layers - the layers for land, water, etc need to get added at an appropriate z-offset to stack together.
- Add textures - the layers should have the texture of the wood that will be used (maple, walnut, cherry etc). I also need to figure how to make these textures shiny (the wood is finished with a semi-gloss shellac). not sure where I’ll get the textures yet…
- Add engraving - for layers with engraved features like streets or buildings I’ll need to draw that engraving onto the surface somehow. 🤔 is that part of the texture?
- Better lighting, controls - these suck right now and will require some experimentation to figure out what looks good.
Sandbox demo: https://codesandbox.io/s/road-layer-preview-test-71k5e?file=/src/App.js
Side note, I’ll need to use a completely different method to create the geometry if I can’t solve my “outline stroke” problem, but hopefully the lighting, controls, etc will carry over even if that happens.
Side side note, I’ve been on a real The Witness kick lately. I replayed the game myself last week, I’ve listened to a few interviews with Jonathan Blow, and I’m currently watching Sean’s playthough on youtube. This is such a brilliant game and I love how it explores concepts of mindfulness and observation through its puzzles. Related to my DK30 project, I would love to map this island, but since it’s got a totally custom game engine I haven’t been able to find any resources online to help with extracting the terrain. If anyone has any idea how to go about doing that I would be extremely grateful!
Day 8 update: I spent the weekend trying to automate the process of going from raw SVG of map data to a laser ready SVG that is properly cropped and framed. This process is becoming more and more straightforward for me in Illustrator, but I’ve had basically no luck replicating it with open source software so far. The biggest obstacle is the “Outline Stroke” function, which is used for converting roads (represented as lines in the raw data) into the topmost layer of the street maps. We basically want to give all the roads a width and then merge them into a single shape with the frame so we can cut away all the negative space.
Attempt 1: I found this JavaScript library that claims to do exactly what I want: you give it a path and it gives you back a shape that is the outline of that path. However, under the hood it’s using a library called potrace that traces raster images and converts them back to vectors. Which means this isn’t really outlining the stroke at all, it’s approximating it with a bitmap. This leads to poor results, especially for rounded joins between line segments and endcaps. On top of that, I haven’t been able to find any way to preserve the coordinate system of the SVG such that I can easily align the output on top of the input (essential for aligning layers on top of each other in the finish product).
Attempt 2: I discovered you can actually run Inkscape from the command line which could be a viable option, and has the advantage of preserving more of an “artboard” than working with raw SVGs. However, some basic attempts to recreate the transformation process in Inkscape were unsuccessful. The “Stroke to Path” function seems to perform extremely poorly compared to its Illustrator counterpart and hangs the application if I run it on anything with a large number of roads (e.g. the map of San Francisco I did the other day). On top of that I couldn’t actually get it to run headless via the command line even for simple inputs since this function still uses the deprecated “verb” functionality that requires the UI to be open. On the bright side, since Inkscape is open source I was able to track down the implementation which might be helpful to port it into my own project. There’s a few other things I might try with Inkscape, namely you can write extensions in Python that allow you to manipulate objects in the artboard more interactively. If the issue with “Stroke to Path” is that it’s an O(n^2) implementation for some reason, I may be able to work around that by performing the operation on each path one at a time.
Next steps: I found a few other open source libraries which look promising, but require more investigation…
- svg-contour might do what I want, but will require some effort to get it working outside of the browser.
- bezier.js has an
outline
function, but I don’t see anything to suggest it supports endcaps / linejoins and it’s also not clear how to convert to / from an SVG. - You can also write Javascript extensions for Adobe Illustrator that I could rig up to get me 90% of the way there on my own desktop at least. That’s definitely not ideal from an online automation standpoint, but I could make it work if I really needed to and it has the benefit of making my own process fully automated.
In doing all of this research and experimentation I also realized that I don’t necessarily need to fully automate the process to deliver an end-to-end customization and preview experience. It would be required to sell laser-ready SVG files, but for generating 2D previews in the browser I can simply rely on built in SVG functionality to arrange / clip the various layers together. For a 3D interactive preview I could then render those SVG layers to a raster image and transform it effectively into a heightmap of 0 / 1 pixels that I can render easily with something like three.js. I think that’s my path forward for the moment… put the transformation automation aside and focus on the in-browser design / preview experience.
Lastly, here’s a couple more maps I made over the weekend to experiment with buildings and paths on a more local level. I really love how the buildings pop in the second map, but I need to figure out how to give them more contrast with the road layer. I’m not terribly happy with the paths in the larger map (they feel pretty messy and random), but overall the contrast is much better.
Day 5 update: I’ve been jamming on a few different maps of coastal cities which have a pretty consistent set of layers (water, land with engraved streets, major roads and then some sort of frame). I’ve been sharing these with friends and posting on Instagram to get feedback on the design and particularly on the hardware used to join the layers. The simplest method (by far) is using these threaded tubes which I’ve learned are called “Chicago screws” and I can get in a variety of colors and lengths for maps with different color schemes or number of layers. Standoffs are another cool option for direct wall mounting, but feedback has been pretty consistent that they’re too bulky and detract from the rest of the design. The other available option is glue which can be quite messy and doesn’t work well with pre-finished pieces, but has the advantage of being the cleanest from a design standpoint. I have a set of frame clamps coming so I can experiment with this a bit more next week.
The other big goal of the week is dialing in the process so it can be reduced to a set of automated operations. I think I’ve managed to reduce that down to a pretty straightforward set of polygon union and subtraction operations which can be replicated with a variety of open-source SVG libraries. Next up is to do some proof-of-concept work to validate that I can transform the raw map data into the desired SVG layers.
Estimated Timeframe
Oct 5th - Nov 1st
Week 1 Goal
Starting early because I’m on sabbatical for the month of October!
Using this first week to dial in the map making process. Two major goals for this week…
- Isolate repeatable processes for getting each layer of data (water, land, roads, etc) into a laser-ready format. Today, I do this by hand in Adobe Illustrator, but I need to automate it and do it in the cloud.
- Make a variety of maps to get a sense of what edge cases might exist with different locations that the software needs to handle, and to experiment with different assembly methods for the finished product.
Anticipated Challenges
- Joining the map layers reliably without breaking these tiny, fragile pieces is going to be a challenge. I’ll need to experiment with some options (e.g. Chicago screws, standoffs, various glues, inlay with engraving) to see what works best and what provides the premium visual finish I’m looking for.
- I’m going to run out of laser-ready hardwood soon and my current supplier is sold out. For this to scale to an actual side-business I need to find some alternative hardwood suppliers and figure out how to sand and finish the wood myself. If I can’t find a suitable supplier to create and sell the maps myself, my backup outcome for the project is that people can design, purchase and download the laser ready files for a few bucks, and then do the laser cutting themselves.
Week 2 Goal
Dialing in the backend software is week two. This will end up as a series of scripts (language TBD) that take an SVG of raw map data as input (e.g. isolate just the roads that go on a single layer) and outputs a laser ready SVG with the inputs composited with the border, text or other elements.
Anticipated challenges
- My process in Illustrator relies heavily on “Outline Stroke” and the “Pathfinder” tools, which seem like they’re probably rather complicated bits of software. Finding open source alternatives for these operations, or learning the math required to make it work well could be tricky. My backup is to do some spelunking in open source vector drawing software like Inkscape to see how those tools work.
Week 3 Goal
Week three is about bringing this process online and setting up the web frontend. I’ll need to build a mapping tool (think Google Maps) that allows people to query, overlay and customize the map data (e.g. roads, rivers, etc). Once the desired elements are chosen, I’ll need to render them into a preview. A basic, 2D SVG of all the layers overlaid is an easy starting point. A 3D WebGL rendered version would be really cool and I think will do a much better job capturing the depth and shadow of these maps. Plus, it’s a good reason to learn some 3D stuff!
Anticipated Challenges
- Openstreetmap APIs are heavily rate limited and I could easily blow out those limits with just a few visitors all hitting my service at the same time. Some options… be very strict about querying too much data at once (e.g. you don’t need to see all the sidewalks in the entire state), heavily cache this data on my own servers to avoid re-querying the OSM API, or find an alternate, paid provider of map data like ArcGIS or Mapbox.
- The UX of this tool is going to be pretty janky at first. I’m not going for the most beautiful website in the world, but if this is basically the storefront for my business it will require some thought. At the very least it needs to be easy to use for someone that has no idea how to write an openstreetmap query. Getting alpha builds into friends’ hands to test and then watching where they get stuck will be important to minimize this risk. Also hoping to get some design feedback from actual designers I work with (I can trade free maps for UX testing / consulting).
Week 4 Goal
Week four is about turning this into an actual storefront so it’s a lot of not-art tasks. I’ll need to figure out how to take orders and receive payments. I probably need to set up some sort of LLC to start selling things. Do I leverage something like Etsy or Shopify to simplify this process? I need to figure out how to price these pieces (based on materials? time? laser wear and tear?). I need to figure out how to ship these safely and how to factor that into the cost. I need to figure out how to brand them so they actually look professional (naming things and coming up with logos is always time consuming).
Anticipated Challenges
- Assuming I can answer most of the basic questions around selling things, how do I manage “inventory”? If each map takes a couple hours to cut, assemble and ship this is going to be a very “nights and weekends” operation so I can really only do a handful of orders at once. Do I limit the number of open orders and then put up a “sold out” sign? I’d typically put having too many orders into a “good problems to have” bucket, but back of the envelope math suggests this will likely be more a labor of love than a serious for-profit venture so limiting the amount of time I need to invest in filling orders might be wise.