├── docs ├── basic.gif ├── custom.gif └── options.gif ├── .gitignore ├── Sketch Maps.sketchplugin └── Contents │ ├── Resources │ ├── icon@2x.png │ ├── fillMap@2x.png │ ├── settings@2x.png │ └── openStudio@2x.png │ └── Sketch │ ├── manifest.json │ ├── main.cocoascript │ └── utils.js ├── LICENSE ├── README.md └── .appcast.xml /docs/basic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcbouchenoire/sketch-maps/HEAD/docs/basic.gif -------------------------------------------------------------------------------- /docs/custom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcbouchenoire/sketch-maps/HEAD/docs/custom.gif -------------------------------------------------------------------------------- /docs/options.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcbouchenoire/sketch-maps/HEAD/docs/options.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .DS_Store? 3 | ._* 4 | .Spotlight-V100 5 | .Trashes 6 | ehthumbs.db 7 | Thumbs.db 8 | -------------------------------------------------------------------------------- /Sketch Maps.sketchplugin/Contents/Resources/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcbouchenoire/sketch-maps/HEAD/Sketch Maps.sketchplugin/Contents/Resources/icon@2x.png -------------------------------------------------------------------------------- /Sketch Maps.sketchplugin/Contents/Resources/fillMap@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcbouchenoire/sketch-maps/HEAD/Sketch Maps.sketchplugin/Contents/Resources/fillMap@2x.png -------------------------------------------------------------------------------- /Sketch Maps.sketchplugin/Contents/Resources/settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcbouchenoire/sketch-maps/HEAD/Sketch Maps.sketchplugin/Contents/Resources/settings@2x.png -------------------------------------------------------------------------------- /Sketch Maps.sketchplugin/Contents/Resources/openStudio@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcbouchenoire/sketch-maps/HEAD/Sketch Maps.sketchplugin/Contents/Resources/openStudio@2x.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marc Bouchenoire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Sketch Maps.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sketch Maps", 3 | "description": 4 | "A plugin that uses the Mapbox API to fill layers with specific and custom maps.", 5 | "author": "Marc Bouchenoire", 6 | "authorEmail": "mail@marcbouchenoire.com", 7 | "homepage": "https://github.com/bouchenoiremarc/Sketch-Maps", 8 | "version": "1.2", 9 | "appcast": 10 | "https://raw.githubusercontent.com/bouchenoiremarc/Sketch-Maps/master/.appcast.xml", 11 | "icon": "settings@2x.png", 12 | "identifier": "com.bouchenoiremarc.sketch-maps", 13 | "commands": [ 14 | { 15 | "script": "main.cocoascript", 16 | "name": "Fill with Map", 17 | "identifier": "addMap", 18 | "description": "Fill selected layer with a Map.", 19 | "icon": "fillMap@2x.png" 20 | }, 21 | { 22 | "script": "main.cocoascript", 23 | "name": "Open Mapbox Studio", 24 | "identifier": "openStudio", 25 | "handler": "openStudio", 26 | "description": "Open Mapbox Studio in your browser.", 27 | "icon": "openStudio@2x.png" 28 | } 29 | ], 30 | "menu": { 31 | "items": ["addMap", "-", "openStudio"] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🗺 Sketch Maps 2 | 3 | Sketch Maps is a plugin that uses the Mapbox API to fill layers with specific and custom maps. 4 | 5 | ## Usage 6 | 7 | ### Basic 8 | 9 | Basic example 10 | 11 | ### Options (Zoom, Bearing and Pitch) 12 | 13 | Options example 14 | 15 | ### Custom Styles 16 | 17 | In addition to 5 professionally designed styles provided by Mapbox, you can use custom styles designed in [Mapbox Studio](https://www.mapbox.com/mapbox-studio/) with their Style URL (`mapbox://styles/username/style`). 18 | 19 | Custom Styles example 20 | 21 | [Swiss Ski Style](https://github.com/mapbox/mapbox-gl-swiss-ski-style) from Mapbox. 22 | 23 | ## Attribution 24 | 25 | If you hide the watermarks, you are legally required to [include proper attribution elsewhere on the document](https://www.mapbox.com/help/attribution/). 26 | 27 | ## Installation 28 | 29 | Make sure you have the latest version of Sketch installed. **(Sketch 40+)** 30 | 31 | 1. [Download the ZIP file of this repository](https://github.com/bouchenoiremarc/Sketch-Maps/archive/master.zip) 32 | 2. Double click on `Sketch Maps.sketchplugin` 33 | 34 | ## Inspiration 35 | 36 | Sketch Maps is heavily inspired by [Map Generator Sketch](https://github.com/eddiesigner/sketch-map-generator) from [Eduardo Gómez](https://twitter.com/_edev). 37 | 38 | ## License 39 | 40 | Sketch Maps is released under the MIT license. See [LICENSE](LICENSE) for details. 41 | -------------------------------------------------------------------------------- /.appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Version 1.0 6 | 7 | 8 | 9 | Version 1.1 10 | 11 | 13 |
  • New icon
  • 14 |
  • New Mapbox default themes
  • 15 |
  • Added "Open Mapbox Studio" command
  • 16 |
  • Added support for Sketch Runner
  • 17 | 18 | ]]> 19 |
    20 | 21 |
    22 | 23 | Version 1.1.1 24 | 25 | 27 |
  • Compatible with Sketch 47+
  • 28 | 29 | ]]> 30 |
    31 | 32 |
    33 | 34 | Version 1.1.2 35 | 36 | 38 |
  • New icon for Sketch 50+
  • 39 | 40 | ]]> 41 |
    42 | 43 |
    44 | 45 | Version 1.2 46 | 47 | 49 |
  • Added support for decimal degrees coordinates
  • 50 | 51 | ]]> 52 |
    53 | 54 |
    55 |
    56 |
    57 | -------------------------------------------------------------------------------- /Sketch Maps.sketchplugin/Contents/Sketch/main.cocoascript: -------------------------------------------------------------------------------- 1 | @import "utils.js" 2 | 3 | var onRun = function(context) { 4 | initContext(context) 5 | 6 | function getMap(coordinates, layer, values) { 7 | var width = layer.frame().width() > 1280 ? 1280 : layer.frame().width(); 8 | var height = layer.frame().height() > 1280 ? 1280 : layer.frame().height(); 9 | var image = get("https://api.mapbox.com/styles/v1/" + values.style + "/static/" + coordinates + "," + values.zoom + "," + values.bearing + "," + values.pitch + "/" + width + "x" + height + "@2x?access_token=" + token); 10 | if (toNSString(image) == null) { 11 | var fill = layer.style().fills().firstObject(); 12 | fill.setFillType(4); 13 | if (MSApplicationMetadata.metadata().appVersion < 47) { 14 | fill.setImage(MSImageData.alloc().initWithImage_convertColorSpace(NSImage.alloc().initWithData(image), false)); 15 | } else { 16 | fill.setImage(MSImageData.alloc().initWithImage(NSImage.alloc().initWithData(image))); 17 | } 18 | layer.style().fills().firstObject().setPatternFillType(1); 19 | } else { 20 | var message = JSON.parse(toNSString(image)).message; 21 | showMessage("🗺 " + message) 22 | return 23 | } 24 | } 25 | 26 | function addMap() { 27 | var currentLayer = (selection.count() > 0) ? selection[0] : false; 28 | if (!currentLayer) { 29 | showMessage("🗺 No layer selected.") 30 | return 31 | } else if (selection.count() > 1) { 32 | showMessage("🗺 You can only fill one layer.") 33 | return 34 | } else if (is.artboard(currentLayer)) { 35 | showMessage("🗺 You can't fill an artboard.") 36 | return 37 | } 38 | 39 | var window = createWindow(), 40 | alert = window[0], 41 | inputs = window[1]; 42 | var response = alert.runModal(); 43 | 44 | var values = { 45 | address: inputs[0].stringValue(), 46 | zoom: parseFloat(inputs[1].stringValue()), 47 | bearing: parseFloat(inputs[2].stringValue()), 48 | pitch: parseFloat(inputs[3].stringValue()), 49 | style: inputs[4].titleOfSelectedItem(), 50 | customStyle: inputs[5].stringValue() 51 | } 52 | 53 | if (response == "1000") { 54 | if (values.address == "") { 55 | showMessage("🗺 You have to specify an address.") 56 | return 57 | } else if (values.zoom < 0 || values.zoom > 20) { 58 | showMessage("🗺 Zoom level must be between 0-20.") 59 | return 60 | } else if (values.bearing < 0 || values.bearing > 360) { 61 | showMessage("🗺 Bearing must be between 0-360.") 62 | return 63 | } else if (values.pitch < 0 || values.pitch > 60) { 64 | showMessage("🗺 Pitch must be between 0-60.") 65 | return 66 | } 67 | 68 | if (values.customStyle == "") { 69 | if (values.style == "Streets") values.style = "mapbox/streets-v10"; 70 | else if (values.style == "Satellite") values.style = "mapbox/satellite-v9"; 71 | else if (values.style == "Satellite Streets") values.style = "mapbox/satellite-streets-v10"; 72 | else if (values.style == "Outdoors") values.style = "mapbox/outdoors-v10"; 73 | else if (values.style == "Light") values.style = "mapbox/light-v9"; 74 | else if (values.style == "Dark") values.style = "mapbox/dark-v9"; 75 | else if (values.style == "Traffic Day") values.style = "mapbox/traffic-day-v2"; 76 | else if (values.style == "Traffic Night") values.style = "mapbox/traffic-night-v2"; 77 | else if (values.style == "Navigation Preview Day") values.style = "mapbox/navigation-preview-day-v2"; 78 | else if (values.style == "Navigation Preview Night") values.style = "mapbox/navigation-preview-night-v2"; 79 | else if (values.style == "Navigation Guidance Day") values.style = "mapbox/navigation-guidance-day-v2"; 80 | else if (values.style == "Navigation Guidance Night") values.style = "mapbox/navigation-guidance-night-v2"; 81 | } else { 82 | var customStyleURL = values.customStyle.match(/^mapbox:\/\/styles\/([^]+\/[^]+)/); 83 | if (customStyleURL) { 84 | values.style = customStyleURL[1]; 85 | } else { 86 | showMessage("🗺 The Custom Style URL appears to be invalid.") 87 | return 88 | } 89 | } 90 | 91 | var coordinates = values.address.match(/(-*[\d|\.]+), *(-*[\d|\.]+)/); 92 | 93 | if (coordinates) { 94 | getMap(coordinates[2] + "," + coordinates[1], currentLayer, values); 95 | } else { 96 | var geocoding = get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + encodeURIComponent(values.address) + '.json?limit=1&access_token=' + token); 97 | var geocodingJSON = JSON.parse(toNSString(geocoding)); 98 | if (geocodingJSON.features.length > 0) { 99 | var coordinates = geocodingJSON.features[0].center; 100 | getMap(coordinates[0] + "," + coordinates[1], currentLayer, values); 101 | } else { 102 | showMessage("🗺 Nothing found at this address.") 103 | return 104 | } 105 | } 106 | } 107 | } 108 | 109 | addMap() 110 | } 111 | 112 | var openStudio = function() { 113 | open("https://www.mapbox.com/studio/"); 114 | } 115 | -------------------------------------------------------------------------------- /Sketch Maps.sketchplugin/Contents/Sketch/utils.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------// 2 | // Context // 3 | //--------------------------------------// 4 | 5 | var app = NSApplication.sharedApplication(), 6 | selection, 7 | plugin, 8 | doc, 9 | page, 10 | artboard; 11 | 12 | function initContext(context) { 13 | doc = context.document, 14 | plugin = context.plugin, 15 | page = doc.currentPage(), 16 | artboard = page.currentArtboard(), 17 | selection = context.selection, 18 | token = "pk.eyJ1IjoiYm91Y2hlbm9pcmVtYXJjIiwiYSI6ImNpemVrd2I5MjAwNTIzM21kOXRiaGRmdnQifQ.UayfYRVvNDSyy2bo23db9w"; 19 | } 20 | 21 | //--------------------------------------// 22 | // URL // 23 | //--------------------------------------// 24 | 25 | function get(url) { 26 | var request = NSURLRequest.requestWithURL(NSURL.URLWithString(url)); 27 | var response = NSURLConnection.sendSynchronousRequest_returningResponse_error(request, null, null); 28 | 29 | return response 30 | } 31 | 32 | function open(url) { 33 | var nsurl = NSURL.URLWithString(url); 34 | NSWorkspace.sharedWorkspace().openURL(nsurl); 35 | } 36 | 37 | //--------------------------------------// 38 | // Layer Types // 39 | //--------------------------------------// 40 | 41 | var is = { 42 | it: function (layer, layerClass) { 43 | return layer.class() === layerClass 44 | }, 45 | page : function (layer) { 46 | return is.it(layer, MSPage) 47 | }, 48 | artboard : function (layer) { 49 | return is.it(layer, MSArtboardGroup) 50 | }, 51 | group : function (layer) { 52 | return is.it(layer, MSLayerGroup) 53 | }, 54 | text : function (layer) { 55 | return is.it(layer, MSTextLayer) 56 | }, 57 | shape : function (layer) { 58 | return is.it(layer, MSShapeGroup) 59 | } 60 | } 61 | 62 | //--------------------------------------// 63 | // Sketch UI // 64 | //--------------------------------------// 65 | 66 | function showMessage(message) { 67 | doc.showMessage(message) 68 | } 69 | 70 | function displayDialog(message, title) { 71 | if (title) { 72 | app.displayDialog(message).withTitle(title) 73 | } else { 74 | app.displayDialog(message) 75 | } 76 | } 77 | 78 | //--------------------------------------// 79 | // Cocoa // 80 | //--------------------------------------// 81 | 82 | function toNSString(data) { 83 | return NSString.alloc().initWithData_encoding(data, NSUTF8StringEncoding); 84 | } 85 | 86 | function createLabel(text, fontSize, bold, frame, opacity) { 87 | var label = NSTextField.alloc().initWithFrame(frame) 88 | label.setStringValue(text) 89 | label.setFont((bold) ? NSFont.boldSystemFontOfSize(fontSize) : NSFont.systemFontOfSize(fontSize)) 90 | label.setBezeled(false) 91 | label.setDrawsBackground(false) 92 | label.setEditable(false) 93 | label.setSelectable(false) 94 | if (opacity) label.setAlphaValue(opacity) 95 | 96 | return label 97 | } 98 | 99 | function createTextField(value, placeholder, frame) { 100 | var textfield = NSTextField.alloc().initWithFrame(frame) 101 | textfield.cell().setWraps(false); 102 | textfield.cell().setScrollable(true); 103 | textfield.setStringValue(value); 104 | if (placeholder) textfield.setPlaceholderString(placeholder); 105 | 106 | return textfield 107 | } 108 | 109 | function createDropdown(values, frame){ 110 | var dropdown = NSPopUpButton.alloc().initWithFrame(frame) 111 | dropdown.addItemsWithTitles(values) 112 | 113 | return dropdown 114 | } 115 | 116 | function createWindow() { 117 | var alert = COSAlertWindow.new() 118 | alert.addButtonWithTitle("OK") 119 | alert.addButtonWithTitle("Cancel") 120 | alert.setMessageText("Sketch Maps") 121 | alert.setInformativeText("Enter an address to fill the selected layer with a map. If you hide the watermarks, you are legally required to include proper attribution elsewhere in the document.") 122 | alert.setIcon(NSImage.alloc().initByReferencingFile(plugin.urlForResourceNamed("icon@2x.png").path())); 123 | 124 | var addressLabel = createLabel("Address", 12, true, NSMakeRect(0, 0, 300, 16)), 125 | addressTextField = createTextField("", "La Tour Eiffel, Paris, France", NSMakeRect(0, 0, 300, 24)), 126 | optionsView = NSView.alloc().initWithFrame(NSMakeRect(0, 0, 300, 136)), 127 | optionsLabel = createLabel("Options", 12, true, NSMakeRect(0, 110, 300, 16)), 128 | zoomLabel = createLabel("Zoom", 12, false, NSMakeRect(0, 86, 300, 16)), 129 | zoomRangeLabel = createLabel("(20)", 12, false, NSMakeRect(36, 86, 300, 16), 0.3), 130 | zoomTextField = createTextField("16", null, NSMakeRect(0, 56, 90, 24)), 131 | bearingLabel = createLabel("Bearing", 12, false, NSMakeRect(105, 86, 300, 16)), 132 | bearingRangeLabel = createLabel("(360)", 12, false, NSMakeRect(152, 86, 300, 16), 0.3), 133 | bearingTextField = createTextField("0", null, NSMakeRect(105, 56, 90, 24)), 134 | pitchLabel = createLabel("Pitch", 12, false, NSMakeRect(210, 86, 300, 16)), 135 | pitchRangeLabel = createLabel("(60)", 12, false, NSMakeRect(242, 86, 300, 16), 0.3), 136 | pitchTextField = createTextField("0", null, NSMakeRect(210, 56, 90, 24)), 137 | styleLabel = createLabel("Style", 12, false, NSMakeRect(0, 30, 300, 16)), 138 | styleDropdown = createDropdown(["Streets", "Satellite", "Satellite Streets", "Outdoors", "Light", "Dark", "Traffic Day", "Traffic Night", "Navigation Preview Day", "Navigation Preview Night", "Navigation Guidance Day", "Navigation Guidance Night"], NSMakeRect(-2, -1, 96, 24)), 139 | customLabel = createLabel("Custom Style URL", 12, false, NSMakeRect(105, 30, 300, 16)), 140 | customOptionalLabel = createLabel("(Optional)", 12, false, NSMakeRect(211, 30, 300, 16), 0.3), 141 | customTextField = createTextField("", "mapbox://styles/", NSMakeRect(105, 0, 195, 24)); 142 | 143 | alert.addAccessoryView(addressLabel); 144 | alert.addAccessoryView(addressTextField); 145 | optionsView.addSubview(optionsLabel); 146 | optionsView.addSubview(zoomLabel); 147 | optionsView.addSubview(zoomRangeLabel); 148 | optionsView.addSubview(zoomTextField); 149 | optionsView.addSubview(bearingLabel); 150 | optionsView.addSubview(bearingRangeLabel); 151 | optionsView.addSubview(bearingTextField); 152 | optionsView.addSubview(pitchLabel); 153 | optionsView.addSubview(pitchRangeLabel); 154 | optionsView.addSubview(pitchTextField); 155 | optionsView.addSubview(styleLabel); 156 | optionsView.addSubview(styleDropdown); 157 | optionsView.addSubview(customLabel); 158 | optionsView.addSubview(customOptionalLabel); 159 | optionsView.addSubview(customTextField); 160 | alert.addAccessoryView(optionsView); 161 | 162 | alert.alert().window().setInitialFirstResponder(addressTextField); 163 | addressTextField.setNextKeyView(zoomTextField); 164 | zoomTextField.setNextKeyView(bearingTextField); 165 | bearingTextField.setNextKeyView(pitchTextField); 166 | 167 | var inputs = [addressTextField, zoomTextField, bearingTextField, pitchTextField, styleDropdown, customTextField]; 168 | 169 | return [alert, inputs] 170 | } 171 | --------------------------------------------------------------------------------