├── 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 |
10 |
11 | ### Options (Zoom, Bearing and Pitch)
12 |
13 |
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 |
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 |
--------------------------------------------------------------------------------