├── templates ├── Map._ └── Metadata._ ├── package.json ├── models └── Project.bones ├── readme.md └── views ├── Metadata.bones └── Map.bones /templates/Map._: -------------------------------------------------------------------------------- 1 |
2 |
Zoom
3 |
4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tilemill-reference-layer", 3 | "description": "Add MapBox Streets or a custom map as a base layer.", 4 | "version": "0.3.0", 5 | "engines": {"tilemill":"0.10.0 || 0.10.1"}, 6 | "keywords": ["tilemill"] 7 | } 8 | -------------------------------------------------------------------------------- /models/Project.bones: -------------------------------------------------------------------------------- 1 | // Extend the Project model schema to allow for extra metadata. 2 | model = models.Project.augment({ 3 | tileJSON: function() { 4 | return 'http://a.tiles.mapbox.com/v3/' + this.get('_basemap') + '.jsonp'; 5 | } 6 | }); 7 | 8 | model.prototype.schema.properties._basemap = { 9 | type: 'string' 10 | }; 11 | 12 | model.prototype.defaults._basemap = ''; 13 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Reference Layer for TileMill 2 | 3 | This TileMill plugin adds MapBox Streets or a custom map as a reference layer to your TileMill projects. 4 | 5 | ## Installation 6 | 7 | The plugin should be available from the Plugins panel in TileMill. You can also install it manually by cloning this repository into your TileMill plugins directory. 8 | 9 | __Note:__ This plugin is not tested to work with other plugins. 10 | 11 | ## Use 12 | 13 | - Enable the plugin 14 | - Go to a projects settings, and enter a map ID from your MapBox.com map in the MapBox Layer field. You can get the map ID from the publish dialog on MapBox.com under mapbox.js. 15 | ![Screen Shot 2012-12-21 at 12 36 53 PM](https://f.cloud.github.com/assets/170641/27627/2bbfda0c-4b95-11e2-8fc3-0db8eb945019.png) 16 | - Design a map with a transparent `Map` background 17 | - Upload your map to [MapBox](http://mapbox.com) 18 | - Add your map to a [composite](http://mapbox.com/hosting/compositing/) with your map 19 | - [Share](http://mapbox.com/hosting/embeds-vs-api/) your map! 20 | -------------------------------------------------------------------------------- /views/Metadata.bones: -------------------------------------------------------------------------------- 1 | view = views.Metadata; 2 | 3 | view.prototype.save = function() { 4 | var attr = Bones.utils.form(this.$('form'), this.model); 5 | var save = attr._saveProject; 6 | var error = function(m, e) { new views.Modal(e); }; 7 | 8 | // Massage values. 9 | if (attr.filename) attr.filename = attr.filename + '.' + this.model.get('format'); 10 | if (attr.bounds) attr.bounds = _(attr.bounds.split(',')).map(parseFloat); 11 | if (attr.center) attr.center = _(attr.center.split(',')).map(parseFloat); 12 | if (attr.zooms) attr.minzoom = attr.zooms[0]; 13 | if (attr.zooms) attr.maxzoom = attr.zooms[1]; 14 | if (attr.format || attr.format_custom) attr.format = attr.format || attr.format_custom; 15 | if (attr.height) attr.height = parseInt(attr.height,10); 16 | if (attr.width) attr.width = parseInt(attr.width,10); 17 | delete attr.zooms; 18 | delete attr.format_custom; 19 | delete attr._saveProject; 20 | attr = _(attr).reduce(function(memo, val, key) { 21 | var allowEmpty = ['description', 'attribution', '_basemap']; 22 | if (val !== '' || _(allowEmpty).include(key)) memo[key] = val; 23 | return memo; 24 | }, {}); 25 | 26 | // Project settings. 27 | if (this.model === this.project) { 28 | if (!this.project.set(attr, {error:error})) return false; 29 | this.project.save({}, { success:this.success, error:error}); 30 | return false; 31 | } 32 | 33 | // Exports. 34 | switch (this.model.get('format')) { 35 | case 'sync': 36 | if (!this.model.set({ 37 | id: this.project.id, 38 | name: this.project.get('name') || this.project.id 39 | }, {error:error})) return false; 40 | break; 41 | case 'mbtiles': 42 | if (!this.model.set({ 43 | filename: attr.filename, 44 | bbox: attr.bounds, 45 | minzoom: attr.minzoom, 46 | maxzoom: attr.maxzoom 47 | }, {error:error})) return false; 48 | break; 49 | case 'png': 50 | case 'pdf': 51 | case 'svg': 52 | if (!this.model.set({ 53 | filename: attr.filename, 54 | bbox: attr.bounds, 55 | width: attr.width, 56 | height: attr.height 57 | }, {error:error})) return false; 58 | break; 59 | } 60 | 61 | // Just save the export. 62 | if (!save) return this.model.save({}, this) && false; 63 | 64 | // Save export and then project. 65 | delete attr.filename; 66 | delete attr.width; 67 | delete attr.height; 68 | if (!this.project.set(attr, {error:error})) return false; 69 | Bones.utils.serial([ 70 | _(function(next) { 71 | this.model.save({}, { success:next, error:this.error }); 72 | }).bind(this), 73 | _(function(next) { 74 | this.project.save({}, this); 75 | }).bind(this)]); 76 | return false; 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /templates/Metadata._: -------------------------------------------------------------------------------- 1 | <% 2 | var get = _(project.get).bind(project); 3 | %> 4 |
5 |
Zoom
6 |
7 | 8 |
129 | -------------------------------------------------------------------------------- /views/Map.bones: -------------------------------------------------------------------------------- 1 | view = Backbone.View.extend(); 2 | 3 | view.prototype.initialize = function() { 4 | _(this).bindAll( 5 | 'render', 6 | 'attach', 7 | 'mapZoom', 8 | 'fullscreen', 9 | 'renderAttach' 10 | ); 11 | this.model.bind('saved', this.attach); 12 | this.model.bind('poll', this.attach); 13 | this.model.bind('change:_basemap', this.renderAttach); 14 | this.render().attach(); 15 | }; 16 | 17 | view.prototype.renderAttach = function() { 18 | this.render().attach(); 19 | } 20 | 21 | view.prototype.render = function(init) { 22 | if (!MM) throw new Error('ModestMaps not found.'); 23 | 24 | $(this.el).html(templates.Map()); 25 | 26 | var map = this.map = new MM.Map('map', new wax.mm.connector(this.model.attributes)); 27 | 28 | // Adapted location interaction - opens in new tab 29 | function locationOn(o) { 30 | if ((o.e.type === 'mousemove' || !o.e.type)) { 31 | return; 32 | } else { 33 | var loc = o.formatter({ format: 'location' }, o.data); 34 | if (loc) { 35 | window.open(loc); 36 | } 37 | } 38 | } 39 | 40 | // Indentify which layer is the TileMill layer 41 | this.map.tmLayer = 0; 42 | 43 | // Get remote map endpoint 44 | var basemap = (this.model.get('_basemap')); 45 | if (basemap) { 46 | 47 | // If tilejson url stored, replace it with map ID 48 | var mapID = basemap.match(/\/v\d\/(.*)\.json/); 49 | if (mapID) { 50 | basemap = mapID[1] || ''; 51 | this.model.set({ '_basemap': basemap }); 52 | } 53 | 54 | basemap = this.model.tileJSON(); 55 | wax.tilejson(basemap, _(function(tilejson) { 56 | // Insert remote map as a layer 57 | this.map.insertLayerAt(0, new wax.mm.connector(tilejson)); 58 | this.map.tmLayer = 1; // Indicate that the TileMill layer has changed 59 | }).bind(this)); 60 | } 61 | 62 | // Add references to all controls onto the map object. 63 | // Allows controls to be removed later on. 64 | this.map.controls = { 65 | interaction: wax.mm.interaction() 66 | .map(this.map) 67 | .tilejson(this.model.attributes) 68 | .on(wax.tooltip() 69 | .parent(this.map.parent).events()) 70 | .on({on: locationOn}), 71 | legend: wax.mm.legend(this.map, this.model.attributes), 72 | zoombox: wax.mm.zoombox(this.map), 73 | zoomer: wax.mm.zoomer(this.map).appendTo(this.map.parent), 74 | fullscreen: wax.mm.fullscreen(this.map).appendTo(this.map.parent) 75 | }; 76 | 77 | // Add image error request handler. "Dedupes" image errors by 78 | // checking against last received image error so as to not spam 79 | // the user with the same errors message for every image request. 80 | this.map.getLayerAt(this.map.tmLayer).requestManager.addCallback('requesterror', _(function(manager, msg) { 81 | $.ajax(msg.url, { error: _(function(resp) { 82 | if (resp.responseText === this._error) return; 83 | this._error = resp.responseText; 84 | new views.Modal(resp); 85 | }).bind(this) }); 86 | }).bind(this)); 87 | 88 | var center = this.model.get('center'); 89 | this.map.setCenterZoom(new MM.Location( 90 | center[1], 91 | center[0]), 92 | center[2]); 93 | this.map.setZoomRange( 94 | this.model.get('minzoom'), 95 | this.model.get('maxzoom')); 96 | this.map.addCallback('zoomed', this.mapZoom); 97 | this.map.addCallback('panned', this.mapZoom); 98 | this.map.addCallback('extentset', this.mapZoom); 99 | this.map.addCallback('resized', this.fullscreen); 100 | this.mapZoom({element: this.map.div}); 101 | 102 | 103 | // Wait for map element to autosize, then draw map 104 | (function waitAndDraw() { 105 | var el = document.getElementById('map'); 106 | if (!el.offsetWidth || !el.offsetHeight) { 107 | window.setTimeout(waitAndDraw, 100); 108 | } else { 109 | map.draw(); 110 | } 111 | })(); 112 | 113 | return this; 114 | }; 115 | 116 | // Catch resize events and add a fullscreen class to the 117 | // project element to handle visibility of components. 118 | // Note that the wax fullscreen control sets a body class that 119 | // we cannot use here as it can be stale (e.g. user routes away 120 | // from a fullscreen'd map, leaving a stale class on the body). 121 | view.prototype.fullscreen = function(e) { 122 | if (this.$('#map').hasClass('wax-fullscreen-map')) { 123 | $('div.project').addClass('fullscreen'); 124 | } else { 125 | $('div.project').removeClass('fullscreen'); 126 | } 127 | this.map.draw(); 128 | }; 129 | 130 | // Set zoom display. 131 | view.prototype.mapZoom = function(e) { 132 | this.$('.zoom-display .zoom').text(this.map.getZoom()); 133 | }; 134 | 135 | view.prototype.attach = function() { 136 | this._error = ''; 137 | 138 | var layer = this.map.getLayerAt(this.map.tmLayer); 139 | layer.provider.options.tiles = this.model.get('tiles'); 140 | layer.provider.options.minzoom = this.model.get('minzoom'); 141 | layer.provider.options.maxzoom = this.model.get('maxzoom'); 142 | layer.setProvider(layer.provider); 143 | 144 | layer.provider.setZoomRange(layer.provider.options.minzoom, 145 | layer.provider.options.maxzoom) 146 | 147 | this.map.setZoomRange(layer.provider.options.minzoom, 148 | layer.provider.options.maxzoom) 149 | 150 | this.map.controls.interaction.tilejson(this.model.attributes); 151 | 152 | if (this.model.get('legend')) { 153 | this.map.controls.legend.content(this.model.attributes); 154 | this.map.controls.legend.appendTo(this.map.parent); 155 | } else { 156 | $(this.map.controls.legend.element()).remove(); 157 | } 158 | 159 | this.map.draw(); 160 | this.mapZoom(); 161 | }; 162 | --------------------------------------------------------------------------------