"),setTimeout((function(){$("#autoLoadError").remove()}),3e3)));activeMap=JSON.parse(window.localStorage.getItem("metroMap")),"undefined"!=typeof autoLoadError&&(autoLoadError+="Loading your last-edited map.")}setMapStyle(activeMap),mapDataVersion=activeMap&&activeMap.global&&activeMap.global.data_version?activeMap.global.data_version:1,compatibilityModeIndicator(),mapSize=setMapSize(activeMap,mapDataVersion>1),loadMapFromObject(activeMap),mapHistory.push(JSON.stringify(activeMap)),"undefined"!=typeof autoLoadError&&($("#announcement").append('
'+autoLoadError+"
"),setTimeout((function(){$("#autoLoadError").remove()}),3e3)),setTimeout((function(){$("#tool-resize-"+gridRows).text("Initial size ("+gridRows+"x"+gridCols+")")}),1e3);var e=window.sessionStorage.getItem("zoomLevel");e&&resizeCanvas(e)}function getMapSize(e){var t=0;if("object"!=typeof e&&(e=JSON.parse(e)),2==mapDataVersion)for(var i in e.points_by_color){for(var a in thisColorHighestValue=Math.max(...Object.keys(e.points_by_color[i].xys).map(Number).filter(Number.isInteger).filter((function(t){return Object.keys(e.points_by_color[i].xys[t]).length>0}))),e.points_by_color[i].xys){if(thisColorHighestValue>=ALLOWED_SIZES[ALLOWED_SIZES.length-2]){t=thisColorHighestValue;break}y=Math.max(...Object.keys(e.points_by_color[i].xys[a]).map(Number).filter(Number.isInteger)),y>thisColorHighestValue&&(thisColorHighestValue=y)}thisColorHighestValue>t&&(t=thisColorHighestValue)}else if(1==mapDataVersion)for(var a in t=Math.max(...Object.keys(e).map(Number).filter(Number.isInteger).filter((function(t){return Object.keys(e[t]).length>0}))),e){if(t>=ALLOWED_SIZES[ALLOWED_SIZES.length-2])break;y=Math.max(...Object.keys(e[a]).map(Number).filter(Number.isInteger).filter((function(t){return Object.keys(e[a][t]).length>0}))),y>t&&(t=y)}return t}function setMapSize(e,t){var i=gridRows;if(t)gridRows=e.global.map_size,gridCols=e.global.map_size;else for(allowedSize of(highestValue=getMapSize(e),ALLOWED_SIZES))if(highestValue0?$("#tool-line-options button.original-rail-line").remove():e.global.lines={bd1038:{displayName:"Red Line"},df8600:{displayName:"Orange Line"},f0ce15:{displayName:"Yellow Line"},"00b251":{displayName:"Green Line"},"0896d7":{displayName:"Blue Line"},"662c90":{displayName:"Purple Line"},a2a2a2:{displayName:"Silver Line"},"000000":{displayName:"Logo"},"79bde9":{displayName:"Rivers"},cfe4a7:{displayName:"Parks"}};var i=1;for(var a in e.global.lines)e.global.lines.hasOwnProperty(a)&&null===document.getElementById("rail-line-"+a)&&(keyboardShortcut=i<11?' data-toggle="tooltip" title="Keyboard shortcut: '+numberKeys[i-1].replace("Digit","")+'"':i<21?' data-toggle="tooltip" title="Keyboard shortcut: Shift + '+numberKeys[i-1].replace("Digit","")+'"':i<31?' data-toggle="tooltip" title="Keyboard shortcut: Alt + '+numberKeys[i-1].replace("Digit","")+'"':"",$("#rail-line-new").before('"),i++);$((function(){$('[data-toggle="tooltip"]').tooltip({container:"body"}),bindRailLineEvents(),resetRailLineTooltips(),$(".visible-xs").is(":visible")&&($("#canvas-container").removeClass("hidden-xs"),$("#tool-export-canvas").click(),$("#try-on-mobile").attr("disabled",!1))}))}}function updateMapObject(e,t,i,a){if(activeMap)var o=activeMap;else o=JSON.parse(window.localStorage.getItem("metroMap"));if(2==mapDataVersion&&"line"==activeTool&&(o.points_by_color[a]&&o.points_by_color[a].xys||(o.points_by_color[a]={xys:{}})),"eraser"==activeTool)return 2==mapDataVersion?(a||(a=getActiveLine(e,t,o)),o.points_by_color&&o.points_by_color[a]&&o.points_by_color[a].xys&&o.points_by_color[a].xys[e]&&o.points_by_color[a].xys[e][t]&&delete o.points_by_color[a].xys[e][t],o.stations&&o.stations[e]&&o.stations[e][t]&&delete o.stations[e][t]):1==mapDataVersion&&o[e]&&o[e][t]&&delete o[e][t],o;if(1==mapDataVersion&&(o.hasOwnProperty(e)?o[e].hasOwnProperty(t)||(o[e][t]={}):(o[e]={},o[e][t]={})),2==mapDataVersion&&"line"==activeTool)for(var n in o.points_by_color[a].xys[e]||(o.points_by_color[a].xys[e]={}),o.points_by_color[a].xys[e][t]=1,o.points_by_color)n!=a&&o.points_by_color[n].xys&&o.points_by_color[n].xys[e]&&o.points_by_color[n].xys[e][t]&&delete o.points_by_color[n].xys[e][t];else 1==mapDataVersion&&"line"==activeTool?o[e][t].line=a:"station"==activeTool&&(2==mapDataVersion?(o.stations||(o.stations={}),o.stations[e]||(o.stations[e]={}),o.stations[e][t]||(o.stations[e][t]={}),o.stations[e][t][i]=a):o[e][t].station[i]=a);return o}function moveMap(e){if("station"!=activeTool||!$("#tool-station-options").is(":visible")){var t=0,i=0;if("left"==e)t=-1;else if("right"==e)t=1;else if("down"==e)i=1;else if("up"==e)i=-1;if(2==mapDataVersion){var a={},o={};for(var n in activeMap.points_by_color)for(var l in a[n]={xys:{}},activeMap.points_by_color[n].xys)for(var r in activeMap.points_by_color[n].xys[l])if(l=parseInt(l),r=parseInt(r),Number.isInteger(l)&&Number.isInteger(r)){if(a[n].xys[l+t]||(a[n].xys[l+t]={}),0==l&&"left"==e)return;if(l==gridCols-1&&"right"==e)return;if(0==r&&"up"==e)return;if(r==gridRows-1&&"down"==e)return;0<=l&&l=gridCols&&(r=gridCols-1),s<0?s=0:s>=gridRows&&(s=gridRows-1),[r,s]}function floodFill(e,t,i,a,o){if(i!=a&&e&&t){var n=!1,l=spanBelow=0,r=[e,t],s={};if(o){var c=document.getElementById("hover-canvas");c.getContext("2d").clearRect(0,0,c.width,c.height)}for(;r.length>0;){for(t=r.pop(),n=e=r.pop();n>=0&&getActiveLine(n,t,activeMap)==i;)n--;for(n++,l=spanBelow=0;n0&&getActiveLine(n,t-1,activeMap)==i?(r.push(n,t-1),l=1):l&&t>0&&getActiveLine(n,t-1,activeMap)!=i&&(l=0),!spanBelow&&ti=!0),t))}}function resetTooltipOrientation(){tooltipOrientation="",window.innerWidth<=768?tooltipOrientation="top":$("#snap-controls-right").is(":hidden")?tooltipOrientation="left":tooltipOrientation="right",$(".has-tooltip").each((function(){$(this).data("bs.tooltip")&&($(this).data("bs.tooltip").options.placement=tooltipOrientation)}))}function resetRailLineTooltips(){for(var e=$(".rail-line"),t="",i=0;iHide grid")}function hideGrid(){$("canvas#grid-canvas").addClass("hide-gridlines"),$("canvas#grid-canvas").css("opacity",0),$("#tool-grid span").html("Show grid")}function setFloodFillUI(){if($("#tool-flood-fill").prop("checked"))$("#tool-line-icon-pencil").hide(),$("#tool-line-caption-draw").hide(),$("#tool-line-icon-paint-bucket").show(),$("#tool-eraser-icon-paint-bucket").show(),$("#tool-eraser-icon-eraser").hide(),$("#tool-eraser-caption-eraser").hide(),menuIsCollapsed||($("#tool-line-caption-fill").show(),$("#tool-eraser-caption-fill").show()),("line"==activeTool&&activeToolOption||"eraser"==activeTool)&&(indicatorColor="line"==activeTool&&activeToolOption?activeToolOption:"#ffffff",floodFill(hoverX,hoverY,getActiveLine(hoverX,hoverY,activeMap),indicatorColor,!0));else{$("#tool-line-icon-pencil").show(),$("#tool-eraser-icon-eraser").show(),$("#tool-line-icon-paint-bucket").hide(),$("#tool-line-caption-fill").hide(),menuIsCollapsed||($("#tool-line-caption-draw").show(),$("#tool-eraser-caption-eraser").show()),$("#tool-eraser-icon-paint-bucket").hide(),$("#tool-eraser-caption-fill").hide(),$("#tool-eraser-options").hide();var e=document.getElementById("hover-canvas");e.getContext("2d").clearRect(0,0,e.width,e.height),("line"==activeTool&&activeToolOption||"eraser"==activeTool)&&(indicatorColor="line"==activeTool&&activeToolOption?activeToolOption:"#ffffff",drawHoverIndicator(hoverX,hoverY,indicatorColor))}}function setURLAfterSave(e){window.location.href.split("=")[0]}function getSurroundingLine(e,t,i){return getActiveLine((e=parseInt(e))-1,t=parseInt(t),i)&&getActiveLine(e-1,t,i)==getActiveLine(e+1,t,i)?getActiveLine(e-1,t,i):getActiveLine(e,t-1,i)&&getActiveLine(e,t-1,i)==getActiveLine(e,t+1,i)?getActiveLine(e,t-1,i):getActiveLine(e-1,t-1,i)&&getActiveLine(e-1,t-1,i)==getActiveLine(e+1,t+1,i)?getActiveLine(e-1,t-1,i):getActiveLine(e-1,t+1,i)&&getActiveLine(e-1,t+1,i)==getActiveLine(e+1,t-1,i)?getActiveLine(e-1,t+1,i):void 0}function setAllStationOrientations(e,t){if(t=parseInt(t),-1!=ALLOWED_ORIENTATIONS.indexOf(t))if(2==mapDataVersion)for(var i in e.stations)for(var a in e.stations[i])i=parseInt(i),a=parseInt(a),e.stations[i][a].orientation=t;else if(1==mapDataVersion)for(var i in e)for(var a in e[i])i=parseInt(i),a=parseInt(a),Number.isInteger(i)&&Number.isInteger(a)&&-1!=Object.keys(e[i][a]).indexOf("station")&&(e[i][a].station.orientation=t)}function resetAllStationStyles(e){if(2==mapDataVersion)for(var t in e.stations)for(var i in e.stations[t])t=parseInt(t),i=parseInt(i),e.stations[t][i].style&&delete e.stations[t][i].style;else if(1==mapDataVersion)for(var t in e)for(var i in e[t])t=parseInt(t),i=parseInt(i),Number.isInteger(t)&&Number.isInteger(i)&&-1!=Object.keys(e[t][i]).indexOf("station")&&-1!=Object.keys(e[t][i].station).indexOf("style")&&delete e[t][i].station.style}function getLineDirection(e,t,i){return e=parseInt(e),t=parseInt(t),origin=getActiveLine(e,t,i),NW=getActiveLine(e-1,t-1,i),NE=getActiveLine(e+1,t-1,i),SW=getActiveLine(e-1,t+1,i),SE=getActiveLine(e+1,t+1,i),N=getActiveLine(e,t-1,i),E=getActiveLine(e+1,t,i),S=getActiveLine(e,t+1,i),W=getActiveLine(e-1,t,i),info={direction:!1,endcap:!1},origin?(origin==W&&W==E?info.direction="horizontal":origin==N&&N==S?info.direction="vertical":origin==NW&&NW==SE?info.direction="diagonal-se":origin==SW&&SW==NE?info.direction="diagonal-ne":origin==W||origin==E?(info.direction="horizontal",info.endcap=!0):origin==N||origin==S?(info.direction="vertical",info.endcap=!0):origin==NW||origin==SE?(info.direction="diagonal-se",info.endcap=!0):origin==SW||origin==NE?(info.direction="diagonal-ne",info.endcap=!0):info.direction="singleton",info):info}function getConnectedStations(e,t,i){if(getStation(e=parseInt(e),t=parseInt(t),i)){var a=getStation(e-1,t-1,i),o=getStation(e+1,t-1,i),n=getStation(e-1,t+1,i),l=getStation(e+1,t+1,i),r=getStation(e,t-1,i),s=getStation(e+1,t,i),c=getStation(e,t+1,i),p=getStation(e-1,t,i);if(!(a||o||n||l||r||s||c||p))return"singleton";var d={highest:{E:0,S:0,SE:0,NE:0},E:{},S:{},N:{},W:{},NE:{},SE:{},SW:{},NW:{}};if(s&&u(s)){var v=e,g=t;do{v+=1}while(u(getStation(v,g,i)));d.E={x1:v-1,y1:g},d.highest.E=v-e-1}if(c&&u(c)){v=e,g=t;do{g+=1}while(u(getStation(v,g,i)));d.S={x1:v,y1:g-1},d.highest.S=g-t-1}if(o&&u(o)){v=e,g=t;do{v+=1,g-=1}while(u(getStation(v,g,i)));d.NE={x1:v-1,y1:g+1},d.highest.NE=v-e-1}if(l&&u(l)){v=e,g=t;do{v+=1,g+=1}while(u(getStation(v,g,i)));d.SE={x1:v-1,y1:g-1},d.highest.SE=v-e-1}if(p&&u(p)){v=e,g=t;do{v-=1}while(u(getStation(v,g,i)));d.W={x1:v+1,y1:g},d.E.internal=!0,d.highest.E+=Math.abs(v-e)-1}if(r&&u(r)){v=e,g=t;do{g-=1}while(u(getStation(v,g,i)));d.N={x1:v,y1:g+1},d.S.internal=!0,d.highest.S+=Math.abs(g-t)-1}if(a&&u(a)){v=e,g=t;do{v-=1,g-=1}while(u(getStation(v,g,i)));d.NW={x1:v+1,y1:g+1},d.SE.internal=!0,d.highest.SE+=Math.abs(v-e)-1}if(n&&u(n)){v=e,g=t;do{v-=1,g+=1}while(u(getStation(v,g,i)));d.SW={x1:v+1,y1:g-1},d.NE.internal=!0,d.highest.NE+=Math.abs(v-e)-1}var m=Object.values(d.highest).filter((e=>e>0)),h=Math.max(...Object.values(m));if(0==m.length)return"singleton";if(m.indexOf(h)!=m.lastIndexOf(h))return"conflicting";var f=Object.keys(d.highest).filter((e=>!0!==Number.isNaN(e))).sort((function(e,t){return d.highest[e]-d.highest[t]})).reverse();return!f||!f[0]||!d[f[0]].internal&&{x0:e,y0:t,x1:d[f[0]].x1,y1:d[f[0]].y1}}function u(e){return!!e&&("rect"==e.style||"rect-round"==e.style||"circles-thin"==e.style||!(e.style||"rect"!=mapStationStyle&&"rect-round"!=mapStationStyle&&"circles-thin"!=mapStationStyle))}}function stretchMap(e){e||(e=activeMap);var t={};if(t.global=Object.assign({},activeMap.global),2==mapDataVersion){for(var i in t.points_by_color={},e.points_by_color)for(var a in t.points_by_color[i]={xys:{}},e.points_by_color[i].xys)for(var o in e.points_by_color[i].xys[a])a=parseInt(a),o=parseInt(o),e.points_by_color[i].xys[a][o]&&(2*a>MAX_MAP_SIZE-1||2*o>MAX_MAP_SIZE-1||(t.points_by_color[i].xys.hasOwnProperty(2*a)||(t.points_by_color[i].xys[2*a]={}),t.points_by_color[i].xys[2*a][2*o]=e.points_by_color[i].xys[a][o]));for(var a in setMapSize(t),resetResizeButtons(gridCols),t.stations={},e.stations)for(var o in e.stations[a])a=parseInt(a),o=parseInt(o),2*a>=gridRows||2*o>=gridCols||(t.stations.hasOwnProperty(2*a)||(t.stations[2*a]={}),t.stations[2*a][2*o]=Object.assign({},e.stations[a][o]));for(a=1;a-1?$("#snap-controls-left").hide():$("#snap-controls-right").hide(),$("#toolbox button span.button-label").show(),$("#title, #remix, #credits, #rail-line-new, #rail-line-change, #rail-line-delete, #straight-line-assist-options, #flood-fill-options, #tool-move-all, #tool-resize-all, #tool-map-style").show(),$("#tool-move-all, #tool-resize-all").removeClass("width-100"),$("#tool-flood-fill").prop("checked")?($("#tool-line-caption-draw").hide(),$("#tool-eraser-caption-eraser").hide()):($("#tool-line-caption-fill").hide(),$("#tool-eraser-caption-fill").hide()),1==$("#hide-save-share-url").length&&$("#hide-save-share-url").show(),"Name this map"==$("#name-this-map").text()&&$("#name-map, #name-this-map").show(),$("#controls-collapse-menu").show(),$("#controls-expand-menu").hide()}}function colorInUse(e){if(!(activeMap&&activeMap.points_by_color&&activeMap.points_by_color[e]&&activeMap.points_by_color[e].xys))return!1;for(var t in activeMap.points_by_color[e].xys)for(var i in activeMap.points_by_color[e].xys[t])if(1==activeMap.points_by_color[e].xys[t][i])return!0}function unfreezeMapControls(){window.innerWidth>768&&$("#tool-line").prop("disabled")&&$("#tool-export-canvas").click(),resetTooltipOrientation()}String.prototype.replaceAll=function(e,t){return this.replace(new RegExp(e,"g"),t)},autoLoad(),$(document).ready((function(){document.getElementById("canvas-container").addEventListener("click",bindGridSquareEvents,!1),document.getElementById("canvas-container").addEventListener("mousedown",bindGridSquareMousedown,!1),document.getElementById("canvas-container").addEventListener("mousemove",throttle(bindGridSquareMouseover,1),!1),document.getElementById("canvas-container").addEventListener("mouseup",bindGridSquareMouseup,!1),window.addEventListener("resize",unfreezeMapControls),window.addEventListener("scroll",(function(){$(".tooltip").hide()})),mouseIsDown=!1,document.getElementById("grid-canvas").addEventListener("contextmenu",disableRightClick),document.getElementById("hover-canvas").addEventListener("contextmenu",disableRightClick),$((function(){$('[data-toggle="tooltip"]').tooltip({container:"body"}),window.innerWidth<=768&&$(".has-tooltip").each((function(){$(this).data("bs.tooltip")&&($(this).data("bs.tooltip").options.placement="top")}))})),document.addEventListener("keydown",(function(e){if(document.activeElement&&"text"==document.activeElement.type)"Enter"==e.key&&("new-rail-line-name"==document.activeElement.id?$("#create-new-rail-line").click():"change-line-name"==document.activeElement.id&&$("#save-rail-line-edits").click(),document.activeElement.blur());else{var t=$(".rail-line"),i=!1;"z"==e.key&&(e.metaKey||e.ctrlKey)&&undo(),"y"==e.key&&(e.metaKey||e.ctrlKey)?e.preventDefault():"c"!=e.key||e.metaKey||e.ctrlKey?"d"==e.key?$("#tool-line").trigger("click"):"e"==e.key?$("#tool-eraser").trigger("click"):"f"==e.key?($("#tool-flood-fill").prop("checked")?$("#tool-flood-fill").prop("checked",!1):$("#tool-flood-fill").prop("checked",!0),setFloodFillUI()):"g"==e.key?$("#straight-line-assist").prop("checked")?$("#straight-line-assist").prop("checked",!1):$("#straight-line-assist").prop("checked",!0):"h"==e.key?$("#tool-grid").trigger("click"):"s"==e.key?$("#tool-station").trigger("click"):"y"!=e.key||e.metaKey||e.ctrlKey?"ArrowLeft"!=e.key||e.metaKey||e.altKey?"ArrowUp"==e.key?(e.preventDefault(),moveMap("up")):"ArrowRight"!=e.key||e.metaKey||e.altKey?"ArrowDown"==e.key?(e.preventDefault(),moveMap("down")):"-"==e.key||"_"==e.key?$("#tool-zoom-out").trigger("click"):"="==e.key||"+"==e.key?$("#tool-zoom-in").trigger("click"):"BracketLeft"==e.code?$("#snap-controls-left").trigger("click"):"BracketRight"==e.code?$("#snap-controls-right").trigger("click"):!e.metaKey&&!e.ctrlKey&&e.shiftKey&&numberKeys.indexOf(e.code)>=0?i=numberKeys.indexOf(e.code)+10:!e.metaKey&&!e.ctrlKey&&e.altKey&&numberKeys.indexOf(e.code)>=0?i=numberKeys.indexOf(e.code)+20:!e.metaKey&&!e.ctrlKey&&numberKeys.indexOf(e.code)>=0&&(i=numberKeys.indexOf(e.code)):(e.preventDefault(),moveMap("right")):(e.preventDefault(),moveMap("left")):menuIsCollapsed||$("#tool-map-style").trigger("click"):menuIsCollapsed?$("#controls-expand-menu").trigger("click"):$("#controls-collapse-menu").trigger("click"),!1!==i&&t[i]&&t[i].click()}})),activeTool="look",$("#toolbox button:not(.rail-line)").on("click",(function(){$(".active").removeClass("active"),$(this).addClass("active"),"line"==activeTool?$("#tool-line").addClass("active"):"eraser"==activeTool?$("#tool-eraser").addClass("active"):"station"==activeTool&&$("#tool-station").addClass("active")})),$("#toolbox button.rail-line").on("click",(function(){$(".active").removeClass("active"),$("#tool-line").addClass("active")})),$("#tool-line").on("click",(function(){$(".active").removeClass("active"),$("#tool-line").addClass("active"),$(this).hasClass("draw-rail-line")&&activeToolOption?(activeTool="line",$(this).css({"background-color":activeToolOption})):"eraser"==activeTool&&activeToolOption?activeTool="line":"eraser"==activeTool&&(activeTool="look"),$("#tool-line-options").is(":visible")?($("#tool-line-options").hide(),$("#tool-new-line-options").hide(),$("#tool-station").hasClass("width-100")||$(this).removeClass("width-100")):($("#tool-line-options").show(),$(this).addClass("width-100")),$(".tooltip").hide()})),$("#tool-flood-fill").change((function(){setFloodFillUI()})),$("#rail-line-delete").click((function(){var e=$(".rail-line"),t=[],i=Object.assign({},activeMap);if("line"==activeTool&&(activeTool="look",$("#tool-line").attr("style",""),$("#tool-line").removeClass("active")),2==mapDataVersion)for(var a of e)colorInUse(a=a.id.slice(10,16))||(t.push($("#rail-line-"+a)),delete activeMap.global.lines[a]);else if(1==mapDataVersion){delete i.global,i=JSON.stringify(i);for(var o=0;o0){autoSave(activeMap);for(var n=0;n-1)$("#tool-save-options").html('
Sorry, there was a problem saving your map: '+e.slice(9)+"
"),console.log("[WARN] Problem was: "+e),$("#tool-save-options").show();else{menuIsCollapsed&&$("#controls-expand-menu").trigger("click");var t=(e=e.split(","))[0].replace(/\s/g,""),i=e[1].replace(/\s/g,""),a='
You can then share this URL with a friend - and they can remix your map without you losing your original! If you make changes to this map, click Save & Share again to get a new URL.
"),$("#tool-save-options").html(""),i&&o&&$("#user-given-map-name").val(o),i&&n&&$("#user-given-map-tags").val(n),$("#name-map").submit((function(e){e.preventDefault()})),$("#map-somewhere-else").click((function(){$("#name-map").show(),$("#name-this-map").show(),$(this).parent().hide(),$("#name-this-map").removeClass(),$("#name-this-map").addClass("styling-blueline width-100"),$("#name-this-map").text("Name this map")})),$("#name-this-map").click((function(e){$("#user-given-map-name").val($("#user-given-map-name").val().replaceAll("<","").replaceAll(">","").replaceAll('"',"").replaceAll("\\\\","").replace("&","&").replaceAll("&","&").replaceAll("/","-").replaceAll("'",""));var t=$("#name-map").serializeArray().reduce((function(e,t){return e[t.name]=t.value,e}),{});window.sessionStorage.setItem("userGivenMapName",$("#user-given-map-name").val()),window.sessionStorage.setItem("userGivenMapTags",$("#user-given-map-tags").val()),csrftoken=getCookie("csrftoken"),t.csrfmiddlewaretoken=csrftoken,$.post("/name/",t,(function(){$("#name-map").hide(),$("#name-this-map").text("Thanks!"),setTimeout((function(){$("#name-this-map").hide()}),500)}))})),i&&o&&n&&($("#user-given-map-name").show(),$("#user-given-map-tags").show(),$("#name-this-map").click(),$("#name-this-map").hide()),$("#tool-save-options").show(),$("#hide-save-share-url").click((function(){$("#tool-save-options").hide()}))}})).fail((function(e){if(400==e.status)var t="Sorry, your map could not be saved. Did you flood fill the whole map? Use flood fill with the eraser to erase and try again.";else if(500==e.status)t="Sorry, your map could not be saved right now. This may be a bug, and the admin has been notified.";else if(e.status>=502)t="Sorry, your map could not be saved right now. Metro Map Maker is currently undergoing routine maintenance including bugfixes and upgrades. Please try again in a few minutes.";$("#tool-save-options").html('
'+t+"
"),$("#tool-save-options").show()})).always((function(){setTimeout((function(){$("#tool-save-map span").text("Save & Share")}),350)})),$(".tooltip").hide()})),$("#tool-download-image").click((function(){activeTool="look",downloadImage(combineCanvases()),$(".tooltip").hide()})),$("#tool-export-canvas").click((function(){if(activeTool="look",drawCanvas(activeMap),$("#tool-station-options").hide(),$(".tooltip").hide(),$("#grid-canvas").is(":visible")){$("#grid-canvas").hide(),$("#hover-canvas").hide(),$("#metro-map-canvas").hide(),$("#metro-map-stations-canvas").hide();var e=document.getElementById("metro-map-canvas"),t=document.getElementById("metro-map-stations-canvas");e.getContext("2d",{alpha:!1}).drawImage(t,0,0);var i=e.toDataURL();$("#metro-map-image").attr("src",i),$("#metro-map-image").show(),$("#export-canvas-help").show(),$("button:not(.mobile-browse)").attr("disabled",!0),$(this).attr("disabled",!1),$(this).attr("title","Go back to editing your map").tooltip("fixTitle").tooltip("show")}else $("#grid-canvas").show(),$("#hover-canvas").show(),$("#metro-map-canvas").show(),$("#metro-map-stations-canvas").show(),$("#metro-map-image").hide(),$("#export-canvas-help").hide(),$("button").attr("disabled",!1),$(this).attr("title","Download your map to share with friends").tooltip("fixTitle").tooltip("show")})),$("#tool-clear-map").click((function(){gridRows=80,gridCols=80,(activeMap={global:Object.assign({},activeMap.global),points_by_color:{},stations:{}}).global.map_size=80,drawGrid(),snapCanvasToGrid(),lastStrokeStyle=void 0,drawCanvas(activeMap,!1,!0),drawCanvas(activeMap,!0,!0),window.sessionStorage.removeItem("userGivenMapName"),window.sessionStorage.removeItem("userGivenMapTags"),$(".resize-grid").each((function(){var e=$(this).attr("id").split("-").slice(2),t=e+" x "+e;e==ALLOWED_SIZES[0]&&(t+=" (Current size)"),$(this).html(t)})),showGrid(),$(".tooltip").hide()})),$("#rail-line-new").click((function(){$("#tool-new-line-options").is(":visible")?($(this).children("span").text("Add New Line"),$("#tool-new-line-options").hide()):($(this).children("span").text("Hide Add Line options"),$("#tool-new-line-options").show())})),$("#create-new-rail-line").click((function(){$("#new-rail-line-name").val($("#new-rail-line-name").val().replaceAll("<","").replaceAll(">","").replaceAll('"',"").replaceAll("\\\\","").replaceAll("/","-"));var e=[],t=[];for(var i in $(".rail-line").each((function(){e.push($(this).attr("id").slice(10,16)),t.push($(this).text())})),""==$("#new-rail-line-color").val()&&$("#new-rail-line-color").val("#000000"),e.indexOf($("#new-rail-line-color").val().slice(1,7))>=0?$("#tool-new-line-errors").text("This color already exists! Please choose a new color."):t.indexOf($("#new-rail-line-name").val())>=0?$("#tool-new-line-errors").text("This rail line name already exists! Please choose a new name."):0==$("#new-rail-line-name").val().length?$("#tool-new-line-errors").text("This rail line name cannot be blank. Please enter a name."):$("#new-rail-line-name").val().length>100?$("#tool-new-line-errors").text("This rail line name is too long. Please shorten it."):$(".rail-line").length>99?$("#tool-new-line-errors").text("Too many rail lines! Delete your unused ones before creating new ones."):($("#tool-new-line-errors").text(""),$("#rail-line-new").before('"),activeMap.global||(activeMap.global={lines:{}}),activeMap.global.lines||(activeMap.global.lines={}),$(".rail-line").each((function(){"rail-line-new"!=$(this).attr("id")&&(activeMap.global.lines[$(this).attr("id").slice(10,16)]={displayName:$(this).text()})})),autoSave(activeMap)),bindRailLineEvents(),resetRailLineTooltips(),$("#tool-lines-to-change").html(""),activeMap.global.lines)$("#tool-lines-to-change").append('")})),$("#rail-line-change").click((function(){for(var e in $("#tool-change-line-options").is(":visible")?($(this).children("span").html("Edit colors & names"),$("#tool-change-line-options").hide()):($(this).children("span").text("Close Edit Line options"),$("#tool-change-line-options").show()),$("#tool-lines-to-change").html(""),$("#change-line-name").hide(),$("#change-line-color").hide(),$("#tool-change-line-options label").hide(),$("#tool-change-line-options p").text(""),activeMap.global.lines)$("#tool-lines-to-change").append('")})),$("#tool-lines-to-change").on("change",(function(){$("#tool-change-line-options label").show(),"Edit which rail line?"!=$("#tool-lines-to-change option:selected").text()?($("#change-line-name").show(),$("#change-line-color").show(),$("#change-line-name").val($("#tool-lines-to-change option:selected").text()),$("#change-line-color").val("#"+$(this).val())):($("#tool-change-line-options p").text(""),$("#change-line-name").hide(),$("#change-line-color").hide())})),$("#save-rail-line-edits").click((function(){if("Edit which rail line?"!=$("#tool-lines-to-change option:selected").text()){var e=$("#tool-lines-to-change").val(),t=$("#change-line-color").val().slice(1),i=$("#tool-lines-to-change option:selected").text(),a=$("#change-line-name").val().replaceAll("<","").replaceAll(">","").replaceAll('"',"").replaceAll("\\\\","").replaceAll("/","-");e!=t&&Object.keys(activeMap.global.lines).indexOf(t)>=0?$("#cant-save-rail-line-edits").text("Can't change "+i+" - it has the same color as "+activeMap.global.lines[t].displayName):(replaceColors({color:e,name:i},{color:t,name:a}),$("#rail-line-change").html("Edit colors & names"),$("#cant-save-rail-line-edits").text(""),$("#tool-change-line-options").hide(),"line"==activeTool&&(activeTool="look",$("#tool-line").attr("style",""),$("#tool-line").removeClass("active")))}"line"==activeTool&&rgb2hex(activeToolOption).slice(1,7)==e&&(activeToolOption="#"+t),resetRailLineTooltips()})),$("#tool-map-style").on("click",(function(){$("#tool-map-style-options").toggle(),$("#tool-map-style-options").is(":visible")||$("#tool-map-style").removeClass("active"),$(".tooltip").hide(),.75==mapLineWidth?$("#tool-map-style-line-750").addClass("active-mapstyle"):$("#tool-map-style-line-"+1e3*mapLineWidth).addClass("active-mapstyle"),$("#tool-map-style-station-"+mapStationStyle).addClass("active-mapstyle")})),$(".map-style-line").on("click",(function(){mapLineWidth="tool-map-style-line-750"==$(this).attr("id")?.75:1/parseInt($(this).data("line-width-divisor")),$(".map-style-line.active-mapstyle").removeClass("active-mapstyle"),$(this).addClass("active-mapstyle"),activeMap&&activeMap.global&&activeMap.global.style?activeMap.global.style.mapLineWidth=mapLineWidth:activeMap&&activeMap.global&&(activeMap.global.style={mapLineWidth:mapLineWidth}),autoSave(activeMap),drawCanvas()})),$(".map-style-station").on("click",(function(){autoSave(activeMap),mapStationStyle=$(this).data("station-style"),$(".map-style-station.active-mapstyle").removeClass("active-mapstyle"),$(this).addClass("active-mapstyle"),$("#reset-all-station-styles").text("Set ALL stations to "+$(this).text()),activeMap&&activeMap.global&&activeMap.global.style?activeMap.global.style.mapStationStyle=mapStationStyle:activeMap&&activeMap.global&&(activeMap.global.style={mapStationStyle:mapStationStyle}),autoSave(activeMap),drawCanvas()})),$("#station-name").change((function(){$(this).val($(this).val().replaceAll('"',"").replaceAll("'","").replaceAll("<","").replaceAll(">","").replaceAll("&","").replaceAll("/","").replaceAll("_"," ").replaceAll("\\\\","").replaceAll("%",""));var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val();Object.keys(temporaryStation).length>0&&(2==mapDataVersion?(activeMap.stations||(activeMap.stations={}),activeMap.stations[e]||(activeMap.stations[e]={}),activeMap.stations[e][t]=Object.assign({},temporaryStation)):activeMap[e][t].station=Object.assign({},temporaryStation),temporaryStation={}),metroMap=updateMapObject(e,t,"name",$("#station-name").val()),autoSave(metroMap),drawCanvas(metroMap,!0)})),$("#station-name-orientation").change((function(){var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val(),i=parseInt($(this).val());e>=0&&t>=0&&(0==i?Object.keys(temporaryStation).length>0?temporaryStation.orientation=0:2==mapDataVersion?activeMap.stations[e][t].orientation=0:1==mapDataVersion&&(activeMap[e][t].station.orientation=0):ALLOWED_ORIENTATIONS.indexOf(i)>=0&&(Object.keys(temporaryStation).length>0?temporaryStation.orientation=i:2==mapDataVersion?activeMap.stations[e][t].orientation=i:1==mapDataVersion&&(activeMap[e][t].station.orientation=i))),window.localStorage.setItem("metroMapStationOrientation",i),0==Object.keys(temporaryStation).length&&autoSave(activeMap),drawCanvas(activeMap,!0),drawIndicator(e,t)})),$("#station-style").on("change",(function(){var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val(),i=$(this).val();e>=0&&t>=0&&(ALLOWED_STYLES.indexOf(i)>=0?Object.keys(temporaryStation).length>0?temporaryStation.style=i:2==mapDataVersion?activeMap.stations[e][t].style=i:1==mapDataVersion&&(activeMap[e][t].station.style=i):i||(2==mapDataVersion&&activeMap.stations[e][t].style?delete activeMap.stations[e][t].style:1==mapDataVersion&&activeMap[e][t].station.style&&delete activeMap[e][t].station.style)),0==Object.keys(temporaryStation).length&&autoSave(activeMap),drawCanvas(activeMap,!0),drawIndicator(e,t)})),$("#station-transfer").click((function(){var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val();e>=0&&t>=0&&($(this).is(":checked")?Object.keys(temporaryStation).length>0?temporaryStation.transfer=1:2==mapDataVersion?activeMap.stations[e][t].transfer=1:1==mapDataVersion&&(activeMap[e][t].station.transfer=1):Object.keys(temporaryStation).length>0?delete temporaryStation.transfer:2==mapDataVersion?delete activeMap.stations[e][t].transfer:1==mapDataVersion&&delete activeMap[e][t].station.transfer),0==Object.keys(temporaryStation).length&&autoSave(activeMap),drawCanvas(activeMap,!0),drawIndicator(e,t)})),$("#loading").remove()})),$("#set-all-station-name-orientation").on("click",(function(){autoSave(activeMap);var e=$("#set-all-station-name-orientation-choice").val();setAllStationOrientations(activeMap,e),drawCanvas(),setTimeout((function(){$("#set-all-station-name-orientation").removeClass("active")}),500)})),$("#reset-all-station-styles").on("click",(function(){resetAllStationStyles(activeMap),autoSave(activeMap),drawCanvas(),setTimeout((function(){$("#reset-all-station-styles").removeClass("active")}),500)})),$("#try-on-mobile").click((function(){editOnSmallScreen()})),$("#i-am-on-desktop").on("click",(function(){editOnSmallScreen(),$("#tool-export-canvas").remove(),$("#tool-download-image").removeClass("hidden-xs")})),$("#controls-collapse-menu").on("click",collapseToolbox),$("#controls-expand-menu").on("click",expandToolbox);
\ No newline at end of file
+var lastStrokeStyle,gridRows=80,gridCols=80,activeTool="look",activeToolOption=!1,activeMap=!1,preferredGridPixelMultiplier=20,redrawOverlappingPoints={},dragX=!1,dragY=!1,clickX=!1,clickY=!1,hoverX=!1,hoverY=!1,temporaryStation={},temporaryLabel={},pngUrl=!1,mapHistory=[],mapRedoHistory=[],MAX_UNDO_HISTORY=100,currentlyClickingAndDragging=!1,mapLineWidth=1,mapLineStyle="solid",activeLineWidth=mapLineWidth,activeLineStyle=mapLineStyle,activeLineWidthStyle=mapLineWidth+"-"+mapLineStyle,mapStationStyle="wmata",menuIsCollapsed=!1,mapSize=void 0,gridStep=5,rulerOn=!1,rulerOrigin=[],MMMDEBUG=!1,MMMDEBUG_UNDO=!1;if(void 0===mapDataVersion)var mapDataVersion=void 0;function compatibilityModeIndicator(){1==mapDataVersion?($(".M:not(.mobile)").css({"background-color":"#bd1038"}),$("#title").css({color:"#bd1038"}),$("#tool-move-v1-warning").attr("style","")):2==mapDataVersion?($(".M:not(.mobile)").css({"background-color":"#df8600"}),$("#title").css({color:"#df8600"}),$("#tool-move-v1-warning").attr("style","display: none")):($(".M:not(.mobile)").css({"background-color":"#000"}),$("#title").css({color:"#000"}),$("#tool-move-v1-warning").attr("style","display: none"))}compatibilityModeIndicator();const numberKeys=["Digit1","Digit2","Digit3","Digit4","Digit5","Digit6","Digit7","Digit8","Digit9","Digit0","Digit1","Digit2","Digit3","Digit4","Digit5","Digit6","Digit7","Digit8","Digit9","Digit0","Digit1","Digit2","Digit3","Digit4","Digit5","Digit6","Digit7","Digit8","Digit9","Digit0"],ALLOWED_LINE_WIDTHS=[100,75,50,25,12.5],ALLOWED_LINE_STYLES=["solid","dashed","dense_thin","dense_thick","dotted_dense","dotted"],ALLOWED_ORIENTATIONS=[0,45,-45,90,-90,135,-135,180,1,-1],ALLOWED_STYLES=["wmata","rect","rect-round","circles-lg","circles-md","circles-sm","circles-thin"],ALLOWED_SIZES=[80,120,160,200,240,360],MAX_MAP_SIZE=ALLOWED_SIZES[ALLOWED_SIZES.length-1];function resizeGrid(e){if(e=parseInt(e),gridRows=e,gridCols=e,3==mapDataVersion)for(var t in activeMap.points_by_color)for(var i in activeMap.points_by_color[t]){for(var a=e;a=e&&activeMap.points_by_color[t][i][a]&&activeMap.points_by_color[t][i][a][o]&&delete activeMap.points_by_color[t][i][a][o],o>=e&&activeMap.stations&&activeMap.stations[a]&&activeMap.stations[a][o]&&delete activeMap.stations[a][o]}else if(2==mapDataVersion)for(var t in activeMap.points_by_color){for(a=e;a=e&&activeMap.points_by_color[t].xys[a]&&activeMap.points_by_color[t].xys[a][o]&&delete activeMap.points_by_color[t].xys[a][o],o>=e&&activeMap.stations&&activeMap.stations[a]&&activeMap.stations[a][o]&&delete activeMap.stations[a][o]}else if(1==mapDataVersion){for(a=e;a=e&&activeMap[a]&&activeMap[a][o]&&delete activeMap[a][o]}for(var t in snapCanvasToGrid(),drawGrid(),lastStrokeStyle=void 0,activeMap.points_by_color)createColorCanvasIfNeeded(t,!0);drawCanvas(activeMap)}function resizeCanvas(e){var t=$("#canvas-container").width();step=gridCols,"out"==e&&t>=800?t-=step:"in"==e&&t<=6400?t+=step:Number.isNaN(e)||(t=parseInt(e)),t<800&&(t=800),t>6400&&(t=6400),window.sessionStorage.setItem("zoomLevel",t),$("#canvas-container").width(t),$("#canvas-container").height(t)}function snapCanvasToGrid(){var e=3600,t=document.getElementById("metro-map-canvas"),i=document.getElementById("metro-map-stations-canvas"),a=document.getElementById("grid-canvas"),o=document.getElementById("hover-canvas"),l=document.getElementById("ruler-canvas");t.height/gridCols!=preferredGridPixelMultiplier&&(gridCols*preferredGridPixelMultiplier<=e?(t.height=gridCols*preferredGridPixelMultiplier,i.height=gridCols*preferredGridPixelMultiplier,a.height=gridCols*preferredGridPixelMultiplier,o.height=gridCols*preferredGridPixelMultiplier,l.height=gridCols*preferredGridPixelMultiplier):(t.height=e,i.height=e,a.height=e,o.height=e,l.height=e),gridRows*preferredGridPixelMultiplier<=e?(t.width=gridRows*preferredGridPixelMultiplier,i.width=gridRows*preferredGridPixelMultiplier,a.width=gridRows*preferredGridPixelMultiplier,o.width=gridRows*preferredGridPixelMultiplier,l.width=gridRows*preferredGridPixelMultiplier):(t.width=e,i.width=e,a.width=e,o.width=e,l.width=e)),$("#canvas-container").height($("#metro-map-canvas").height()),$("#canvas-container").width($("#metro-map-canvas").height())}function coordinateInColor(e,t,i,a,o){if(3==mapDataVersion){if(i.points_by_color[a]&&i.points_by_color[a][o]&&i.points_by_color[a][o][e]&&i.points_by_color[a][o][e][t])return!0}else if(2==mapDataVersion){if(i.points_by_color[a]&&i.points_by_color[a].xys&&i.points_by_color[a].xys[e]&&i.points_by_color[a].xys[e][t])return!0}else if(1==mapDataVersion)return a?getActiveLine(e,t,i)==a:getActiveLine(e,t,i);return!1}function getActiveLine(e,t,i,a){if(3==mapDataVersion&&i.global.lines){for(var o in i.points_by_color)for(var l in i.points_by_color[o])if(coordinateInColor(e,t,i,o,l))return a?[o,l]:o}else if(2==mapDataVersion&&i.global.lines){for(var o in i.points_by_color)if(coordinateInColor(e,t,i,o))return o}else{if(i&&i[e]&&i[e][t]&&i[e][t].line)return i[e][t].line;if(!i)return!1}}function getStation(e,t,i){return i&&(2==mapDataVersion||3==mapDataVersion)&&i.stations&&i.stations[e]?i.stations[e][t]:i&&i[e]&&i[e][t]&&i[e][t].station?i[e][t].station:!!i&&void 0}function getLabel(e,t,i){return i&&(2==mapDataVersion||3==mapDataVersion)&&i.labels&&i.labels[e]?i.labels[e][t]:!!i&&void 0}function moveLineStroke(e,t,i,a,o){e.moveTo(t*gridPixelMultiplier,i*gridPixelMultiplier),e.lineTo(a*gridPixelMultiplier,o*gridPixelMultiplier),singleton=!1}function determineDarkOrLightContrast(e){total=0,rgb=[...e.matchAll(/(\d+)/g)];for(var t=0;t=3),activeToolOption,!0),$("#tool-line-icon-pencil").hide(),$("#tool-line-icon-paint-bucket").show()):(drawNewHoverIndicator(),$("#tool-line-icon-pencil").show(),$("#tool-line-icon-paint-bucket").hide()),$("#tool-station-options").hide()}))}function makeLine(e,t,i){if(1==mapDataVersion)drawArea(e,t,activeMap,!0);else if(mapDataVersion>=2)var a=getActiveLine(e,t,activeMap);var o=rgb2hex(activeToolOption).slice(1,7);metroMap=updateMapObject(e,t,"line",o),i||autoSave(metroMap),mapDataVersion>=2?(a&&redrawCanvasForColor(a),redrawCanvasForColor(o)):1==mapDataVersion&&drawArea(e,t,activeMap)}function redrawCanvasForColor(e){var t=performance.now();drawCanvas(!1,!1,!0),drawColor(e);var i=document.getElementById("metro-map-canvas").getContext("2d",{alpha:!0});for(var e in activeMap.points_by_color){var a=createColorCanvasIfNeeded(e);i.drawImage(a,0,0)}drawCanvas(activeMap,!0);var o=performance.now();MMMDEBUG&&console.log("redrawCanvasForColor finished in "+(o-t)+"ms")}function makeStation(e,t){if(temporaryStation={},!getActiveLine(e,t,activeMap))return activeToolOption?$("#tool-station").removeClass("width-100"):($("#tool-station").addClass("active"),$("#tool-line").removeClass("width-100"),$("#tool-line").removeClass("active")),$("#tool-station-options").hide(),void drawCanvas(activeMap,!0);$("#station-name").val(""),$("#station-coordinates-x").val(e),$("#station-coordinates-y").val(t);$(".rail-line");if($("#tool-station").addClass("width-100"),getStation(e,t,activeMap)){if(getStation(e,t,activeMap).name){var i=getStation(e,t,activeMap).name.replaceAll("_"," ");$("#station-name").val(i),getStation(e,t,activeMap).transfer?$("#station-transfer").prop("checked",!0):$("#station-transfer").prop("checked",!1),document.getElementById("station-name-orientation").value=parseInt(getStation(e,t,activeMap).orientation),document.getElementById("station-style").value=getStation(e,t,activeMap).style||""}}else{temporaryStation={name:""},$("#station-transfer").prop("checked",!1);var a=window.localStorage.getItem("metroMapStationOrientation");a?(document.getElementById("station-name-orientation").value=a,$("#station-name-orientation").change()):document.getElementById("station-name-orientation").value=0,document.getElementById("station-style").value=""}getActiveLine(e,t,activeMap)&&(drawCanvas(activeMap,!0),drawIndicator(e,t),$("#tool-station-options").show()),$("#station-name").focus()}function makeLabel(e,t){temporaryLabel={},$("#tool-label-options").show(),$("#label-coordinates-x").val(e),$("#label-coordinates-y").val(t);var i=getLabel(e,t,activeMap);if(i)i.text&&$("#label-text").val(i.text.replaceAll("_"," ")),i.shape&&$("#label-shape").val(i.shape),i["text-color"]&&$("#label-text-color").val(i["text-color"]),i["bg-color"]?$("#label-bg-color").val(i["bg-color"]):($("#label-bg-color").hide(),$("#label-bg-color-transparent").prop("checked",!0));else{temporaryLabel={text:"",shape:"","text-color":"","bg-color":""},$("#label-text").val("");var a=window.localStorage.getItem("lastLabelShape");a&&(document.getElementById("label-shape").value=a,$("#label-shape").trigger("change"))}drawCanvas(activeMap,!0),drawLabelIndicator(e,t),$("#label-text").focus()}function bindGridSquareEvents(e){if($("#station-coordinates-x").val(""),$("#station-coordinates-y").val(""),e.isTrusted)t=getCanvasXY(e.pageX,e.pageY);else var t=getCanvasXY(dragX,dragY);var i=t[0],a=t[1];if(e.isTrusted){if(currentlyClickingAndDragging)return}else{if(i==clickX||a==clickY);else if(Math.abs(i-clickX)==Math.abs(a-clickY));else if($("#straight-line-assist").prop("checked"))return;currentlyClickingAndDragging=!0}if("line"==activeTool)if($("#tool-flood-fill").prop("checked")){var o=getActiveLine(i,a,activeMap),l=rgb2hex(activeToolOption).slice(1,7);floodFill(i,a,getActiveLine(i,a,activeMap,mapDataVersion>=3),l),autoSave(activeMap),mapDataVersion>=2?(drawColor(o),redrawCanvasForColor(l)):1==mapDataVersion&&drawCanvas(activeMap)}else makeLine(i,a);else if("eraser"==activeTool){var r=getActiveLine(i,a,activeMap);if(!r)return;if(getStation(i,a,activeMap)||getLabel(i,a,activeMap))var n=!0;else n=!1;if(getLabel(i,a,activeMap));else;r&&$("#tool-flood-fill").prop("checked")?(floodFill(i,a,getActiveLine(i,a,activeMap,mapDataVersion>=3),""),autoSave(activeMap),mapDataVersion>=2?redrawCanvasForColor(r):1==mapDataVersion&&drawCanvas(activeMap)):(metroMap=updateMapObject(i,a),autoSave(metroMap),mapDataVersion>=2?redrawCanvasForColor(r):1==mapDataVersion&&drawArea(i,a,metroMap,r,n))}else"station"==activeTool?makeStation(i,a):"label"==activeTool&&makeLabel(i,a)}function bindGridSquareMouseover(e){if($("#ruler-xy").text(getCanvasXY(e.pageX,e.pageY)),xy=getCanvasXY(e.pageX,e.pageY),hoverX=xy[0],hoverY=xy[1],mouseIsDown||$("#tool-flood-fill").prop("checked")){if(!mouseIsDown&&(activeToolOption||"eraser"==activeTool)&&$("#tool-flood-fill").prop("checked")){if("line"==activeTool&&activeToolOption)indicatorColor=activeToolOption;else{if("line"!=activeTool&&"eraser"!=activeTool)return void drawHoverIndicator(e.pageX,e.pageY);indicatorColor="#ffffff"}floodFill(hoverX,hoverY,getActiveLine(hoverX,hoverY,activeMap,mapDataVersion>=3),indicatorColor,!0)}}else drawHoverIndicator(e.pageX,e.pageY),rulerOn&&rulerOrigin.length>0&&("look"==activeTool||"line"==activeTool||"eraser"==activeTool)&&drawRuler(hoverX,hoverY);!mouseIsDown||"line"!=activeTool&&"eraser"!=activeTool||(dragX=e.pageX,dragY=e.pageY,$("#canvas-container").click()),mouseIsDown&&rulerOn&&rulerOrigin.length>0&&("look"==activeTool||"line"==activeTool||"eraser"==activeTool)&&drawRuler(hoverX,hoverY)}function bindGridSquareMouseup(e){if("station"==activeTool&&"text"!=document.activeElement.type&&$("#station-name").focus(),clickX=!1,clickY=!1,mouseIsDown=!1,drawHoverIndicator(e.pageX,e.pageY),rulerOn&&rulerOrigin.length>0&&("line"==activeTool||"eraser"==activeTool)){rulerOrigin=[];var t=document.getElementById("ruler-canvas");t.getContext("2d").clearRect(0,0,t.width,t.height)}}function bindGridSquareMousedown(e){if(xy=getCanvasXY(e.pageX,e.pageY),clickX=xy[0],clickY=xy[1],$("#straight-line-assist").prop("checked")&&("line"==activeTool||"eraser"==activeTool))for(var t=0;t0)gridCols<=240?(o=gridPixelMultiplier/2,i<10?a=i*gridPixelMultiplier+gridPixelMultiplier/4+2:i<100?a=i*gridPixelMultiplier+gridPixelMultiplier/4:i>=100&&(a=i*gridPixelMultiplier+gridPixelMultiplier/4-4)):gridCols>240&&(o=gridPixelMultiplier/1.25,i<10?a=i*gridPixelMultiplier+gridPixelMultiplier/4:i<100?a=i*gridPixelMultiplier+gridPixelMultiplier/4-3:i<1e3&&(a=i*gridPixelMultiplier+gridPixelMultiplier/4-6)),t.fillText(i,a,o),t.fillText(i,a,e.height-gridPixelMultiplier/4)}else t.strokeStyle="#80CEFF";t.beginPath(),t.moveTo(i*gridPixelMultiplier+gridPixelMultiplier/2,0),t.lineTo(i*gridPixelMultiplier+gridPixelMultiplier/2,e.height),t.stroke(),t.closePath()}for(var l=0;l0&&t.fillText(l,e.width-r.width-gridPixelMultiplier/4,l*gridPixelMultiplier+gridPixelMultiplier/2+3)}else t.strokeStyle="#80CEFF";t.beginPath(),t.moveTo(0,l*gridPixelMultiplier+gridPixelMultiplier/2),t.lineTo(e.width,l*gridPixelMultiplier+gridPixelMultiplier/2),t.stroke(),t.closePath()}gridStep?$("#grid-step").text(gridStep):$("#grid-step").text("off")}function getRedrawSection(e,t,i,a){redrawSection={};for(var o=-1*(a=parseInt(a));o<=a;o+=1)for(var l=-1*a;l<=a;l+=1)getActiveLine(e+o,t+l,i)&&(redrawSection.hasOwnProperty(e+o)||(redrawSection[e+o]={}),redrawSection[e+o][t+l]=!0);return redrawSection}function drawArea(e,t,i,a,o){var l=document.getElementById("metro-map-canvas"),r=l.getContext("2d",{alpha:!1});gridPixelMultiplier=l.width/gridCols,fontSize=gridPixelMultiplier;for(var e in e=parseInt(e),t=parseInt(t),r.lineWidth=gridPixelMultiplier*activeLineWidth,r.lineCap="round","eraser"==activeTool&&a&&drawPoint(r,e,t,i,a),redrawSection=getRedrawSection(e,t,i,1),redrawSection)for(var t in redrawSection[e])lastStrokeStyle=void 0,e=parseInt(e),t=parseInt(t),"line"==activeTool&&a?drawPoint(r,e,t,i,getActiveLine(e,t,i)):drawPoint(r,e,t,i);if(redrawSection&&!o){var n=(s=document.getElementById("metro-map-stations-canvas")).getContext("2d",{alpha:!0});for(var e in n.font="700 "+fontSize+"px sans-serif",redrawSection)for(var t in redrawSection[e])drawStation(n,e=parseInt(e),t=parseInt(t),i,!0),drawLabel(n,e,t,i)}else if(o){var s;if((n=(s=document.getElementById("metro-map-stations-canvas")).getContext("2d",{alpha:!0})).clearRect(0,0,s.width,s.height),n.font="700 "+fontSize+"px sans-serif",2==mapDataVersion||3==mapDataVersion){for(var e in i.stations)for(var t in i.stations[e])drawStation(n,e=parseInt(e),t=parseInt(t),i);for(var e in i.labels)for(var t in i.labels[e])drawLabel(n,e=parseInt(e),t=parseInt(t),i)}else if(1==mapDataVersion)for(var e in i)for(var t in i[e])e=parseInt(e),t=parseInt(t),Number.isInteger(e)&&Number.isInteger(t)&&drawStation(n,e,t,i)}}function drawColor(e){if(e){var t=createColorCanvasIfNeeded(e),i=t.getContext("2d",{alpha:!0});if(i.clearRect(0,0,t.width,t.height),3==mapDataVersion)for(var a in activeMap.points_by_color[e]){i.strokeStyle="#"+e;var o=a.split("-")[0]*gridPixelMultiplier,l=a.split("-")[1],r=(v=findLines(e,a)).lines,n=v.singletons;for(var s of r)i.beginPath(),i.lineWidth=o,setLineStyle(l,i),moveLineStroke(i,s[0],s[1],s[2],s[3]),i.stroke(),i.closePath();for(var c of n){var p=(g=c.split(","))[0],d=g[1];i.strokeStyle="#"+e,drawPoint(i,p,d,activeMap,!1,e,o,l)}}else if(2==mapDataVersion){i.strokeStyle="#"+e,activeMap&&activeMap.global&&activeMap.global.style?i.lineWidth=(activeMap.global.style.mapLineWidth||1)*gridPixelMultiplier:i.lineWidth=mapLineWidth*gridPixelMultiplier,i.lineCap="round";var v;r=(v=findLines(e)).lines,n=v.singletons;for(var s of r)i.beginPath(),moveLineStroke(i,s[0],s[1],s[2],s[3]),i.stroke(),i.closePath();for(var c of n){var g;p=(g=c.split(","))[0],d=g[1];i.strokeStyle="#"+e,drawPoint(i,p,d,activeMap,!1,e)}}}}function createColorCanvasIfNeeded(e,t){if(!(i=document.getElementById("metro-map-color-canvas-"+e))){var i,a=document.getElementById("metro-map-canvas"),o=document.getElementById("color-canvas-container");(i=document.createElement("canvas")).id="metro-map-color-canvas-"+e,i.classList="hidden",i.width=a.width,i.height=a.height,o.appendChild(i)}if(t){a=document.getElementById("metro-map-canvas");i.width=a.width,i.height=a.height}return i}function findLines(e,t){if(2!=mapDataVersion||t||(t="xys"),2!=mapDataVersion&&3!=mapDataVersion||activeMap&&activeMap.points_by_color&&activeMap.points_by_color[e]&&activeMap.points_by_color[e][t]){var i={E:new Set,S:new Set,NE:new Set,SE:new Set},a=[],o=new Set,l=new Set;for(var r of["E","S","NE","SE"])for(var n in activeMap.points_by_color[e][t])for(var s in activeMap.points_by_color[e][t][n]){var c=n+","+s;if(!i[r].has(c)){var p=findEndpointOfLine(n,s,activeMap.points_by_color[e][t],r);if(p)for(var d of(a.push([parseInt(n),parseInt(s),p.x1,p.y1]),l.add(c),l.add(p.x1+","+p.y1),p.between))l.add(d),i[r].add(d);else l.has(c)||o.add(c)}}if("function"==typeof o.difference)var v=o.difference(l);else{v=new Set;for(var g of o)l.has(g)||v.add(g)}return{lines:a,singletons:v}}}function findEndpointOfLine(e,t,i,a){var o=[e+","+t];directions={E:{dx:1,dy:0},S:{dx:0,dy:1},NE:{dx:1,dy:-1},SE:{dx:1,dy:1},SW:{dx:-1,dy:1}};var l=directions[a].dx,r=directions[a].dy,n=parseInt(e)+l,s=parseInt(t)+r;if(i&&i[e]&&i[e][t]&&i[n]&&i[n][s]){for(;i[n]&&i[n][s];){o.push(n+","+s);n=parseInt(n)+l,s=parseInt(s)+r}var c=o[o.length-1].split(",");return{between:o,x1:n=parseInt(c[0]),y1:s=parseInt(c[1])}}}function drawCanvas(e,t,i){if(t0=performance.now(),t);else{var a=(d=document.getElementById("metro-map-canvas")).getContext("2d",{alpha:!1});gridPixelMultiplier=Math.floor(d.width/gridCols);var o=gridPixelMultiplier;if(a.fillStyle="#ffffff",a.fillRect(0,0,d.width,d.height),i)return;if(e||(e=activeMap),activeMap=e,a.lineWidth=mapLineWidth*gridPixelMultiplier,a.lineCap="round",mapDataVersion>=2)for(var l in e.points_by_color){drawColor(l);var r=document.getElementById("metro-map-color-canvas-"+l);a.drawImage(r,0,0)}else if(1==mapDataVersion){for(var n in e)for(var s in e[n])n=parseInt(n),s=parseInt(s),Number.isInteger(n)&&Number.isInteger(s)&&drawPoint(a,n,s,e);for(var c=Object.keys(redrawOverlappingPoints).reverse(),p=0;p0&&"https://metromapmaker.com/"==m.slice(0,26)){var h="Remix this map! Go to "+m;g=a.measureText(h).width;a.fillText(h,gridRows*gridPixelMultiplier-g,gridCols*gridPixelMultiplier-25)}t1=performance.now(),MMMDEBUG&&console.log("drawCanvas(map, "+t+", "+i+") finished in "+(t1-t0)+"ms")}function drawPoint(e,t,i,a,o,l,r,n){if(t=parseInt(t),i=parseInt(i),(l=l||getActiveLine(t,i,a))||"eraser"==activeTool){if(e.beginPath(),lastStrokeStyle&&lastStrokeStyle==l||(e.strokeStyle="#"+l,lastStrokeStyle=l),o?(e.strokeStyle="#ffffff",l=o):(e.lineWidth=gridPixelMultiplier*(r||activeLineWidth),setLineStyle(n||activeLineStyle,e)),singleton=!0,r&&n)var s=r+"-"+n;else s=activeLineWidthStyle;coordinateInColor(t+1,i+1,a,l,s)&&(moveLineStroke(e,t,i,t+1,i+1),redrawOverlappingPoints[t]||(redrawOverlappingPoints[t]={}),redrawOverlappingPoints[t][i]=!0),coordinateInColor(t-1,i-1,a,l,s)&&moveLineStroke(e,t,i,t-1,i-1),coordinateInColor(t+1,i-1,a,l,s)&&moveLineStroke(e,t,i,t+1,i-1),coordinateInColor(t-1,i+1,a,l,s)&&moveLineStroke(e,t,i,t-1,i+1),coordinateInColor(t+1,i,a,l,s)&&moveLineStroke(e,t,i,t+1,i),coordinateInColor(t-1,i,a,l,s)&&moveLineStroke(e,t,i,t-1,i),coordinateInColor(t,i+1,a,l,s)&&moveLineStroke(e,t,i,t,i+1),coordinateInColor(t,i-1,a,l,s)&&moveLineStroke(e,t,i,t,i-1);var c=getStation(t,i,a);singleton?(e.fillStyle=o?"#ffffff":"#"+l,"rect"==mapStationStyle||c&&"rect"==c.style?e.fillRect((t-.5)*gridPixelMultiplier,(i-.5)*gridPixelMultiplier,gridPixelMultiplier,gridPixelMultiplier):"circles-md"==mapStationStyle||c&&"circles-md"==c.style?(e.arc(t*gridPixelMultiplier,i*gridPixelMultiplier,.7*gridPixelMultiplier,0,2*Math.PI,!0),e.fill()):"circles-sm"==mapStationStyle||c&&"circles-sm"==c.style?(e.arc(t*gridPixelMultiplier,i*gridPixelMultiplier,.5*gridPixelMultiplier,0,2*Math.PI,!0),e.fill()):(e.arc(t*gridPixelMultiplier,i*gridPixelMultiplier,.9*gridPixelMultiplier,0,2*Math.PI,!0),e.fill())):e.stroke(),e.closePath()}}function drawStation(e,t,i,a,o){var l=getStation(t,i,a);if(l){var r=l.transfer,n=l.style||mapStationStyle,s=!1;if(n&&"wmata"!=n)if("circles-lg"==n){drawStyledStation_WMATA(e,t,i,a,r,"#"+getActiveLine(t,i,a))}else"circles-md"==n?drawCircleStation(e,t,i,a,r,.3,gridPixelMultiplier/2):"circles-sm"==n?drawCircleStation(e,t,i,a,r,.25,gridPixelMultiplier/4):"rect"==n?s=drawStyledStation_rectangles(e,t,i,a,r,0,0):"rect-round"!=n&&"circles-thin"!=n||(s=drawStyledStation_rectangles(e,t,i,a,r,0,0,20));else drawStyledStation_WMATA(e,t,i,a,r);o||drawStationName(e,t,i,a,r,s)}}function drawStationName(e,t,i,a,o,l){e.textAlign="start",e.fillStyle="#000000",e.save();var r=getStation(t,i,a),n=r.name.replaceAll("_"," "),s=parseInt(r.orientation),c=e.measureText(n).width;if(o)var p=1.5*gridPixelMultiplier;else if(l)p=gridPixelMultiplier;else p=.75*gridPixelMultiplier;var d=.25*gridPixelMultiplier;-45==s?(e.translate(t*gridPixelMultiplier,i*gridPixelMultiplier),e.rotate(Math.PI/180*-45),e.fillText(n,p,d)):45==s?(e.translate(t*gridPixelMultiplier,i*gridPixelMultiplier),e.rotate(Math.PI/180*45),e.fillText(n,p,d)):-90==s?(e.translate(t*gridPixelMultiplier,i*gridPixelMultiplier),e.rotate(Math.PI/180*-90),e.fillText(n,-1*c-p,d)):90==s?(e.translate(t*gridPixelMultiplier,i*gridPixelMultiplier),e.rotate(Math.PI/180*-90),e.fillText(n,p,d)):135==s?(e.translate(t*gridPixelMultiplier,i*gridPixelMultiplier),e.rotate(Math.PI/180*-45),e.fillText(n,-1*c-p,d)):-135==s?(e.translate(t*gridPixelMultiplier,i*gridPixelMultiplier),e.rotate(Math.PI/180*45),e.fillText(n,-1*c-p,d)):180==s?o?e.fillText(n,t*gridPixelMultiplier-1.5*gridPixelMultiplier-c,i*gridPixelMultiplier+gridPixelMultiplier/4):e.fillText(n,t*gridPixelMultiplier-gridPixelMultiplier-c,i*gridPixelMultiplier+gridPixelMultiplier/4):1==s?(e.textAlign="center",o?e.fillText(n,t*gridPixelMultiplier,(i-1.5)*gridPixelMultiplier):e.fillText(n,t*gridPixelMultiplier,(i-1)*gridPixelMultiplier)):-1==s?(e.textAlign="center",o?e.fillText(n,t*gridPixelMultiplier,(i+2.25)*gridPixelMultiplier):e.fillText(n,t*gridPixelMultiplier,(i+1.75)*gridPixelMultiplier)):o?e.fillText(n,t*gridPixelMultiplier+1.5*gridPixelMultiplier,i*gridPixelMultiplier+gridPixelMultiplier/4):e.fillText(n,t*gridPixelMultiplier+gridPixelMultiplier,i*gridPixelMultiplier+gridPixelMultiplier/4),e.restore()}function drawStyledStation_WMATA(e,t,i,a,o,l,r){l||(l="#000000"),r||(r="#ffffff"),o&&(drawCircleStation(e,t,i,a,o,1.2,0,l,0,!0),drawCircleStation(e,t,i,a,o,.9,0,r,0,!0)),drawCircleStation(e,t,i,a,o,.6,0,l,0,!0),drawCircleStation(e,t,i,a,o,.3,0,r,0,!0)}function drawCircleStation(e,t,i,a,o,l,r,n,s,c){o&&!n&&(n="#"+getActiveLine(t,i,a)),!s&&o&&mapLineWidth>=.5&&(s="#ffffff",r=gridPixelMultiplier/2),e.fillStyle=n||"#ffffff",e.beginPath(),e.arc(t*gridPixelMultiplier,i*gridPixelMultiplier,gridPixelMultiplier*l,0,2*Math.PI,!0),e.closePath(),c||(e.lineWidth=r,e.strokeStyle=s||"#"+getActiveLine(t,i,a),e.stroke()),e.fill()}function drawStyledStation_rectangles(e,t,i,a,o,l,r,n,s){var c=getActiveLine(t,i,a,!0);if(3==mapDataVersion)var p="#"+c[0],d=c[1].split("-")[0];else if(2==mapDataVersion)p="#"+c,d=mapLineWidth;else if(1==mapDataVersion)p="#"+c,d=1;var v=getLineDirection(t,i,a).direction,g=[],m=!1,h=getConnectedStations(t,i,a),u=getStation(t,i,a),f=gridPixelMultiplier,y=gridPixelMultiplier;if(d>=.5&&"singleton"!=v||"singleton"==v&&("rect-round"==mapStationStyle||u&&"rect-round"==u.style)?(e.strokeStyle="#000000",e.fillStyle="#ffffff"):(e.strokeStyle=p,e.fillStyle=p),!0===h)f=gridPixelMultiplier/2;else if(u&&h&&"singleton"!=h&&"conflicting"!=h)dx=h.x1-h.x0,dy=h.y1-h.y0,f=(Math.abs(dx)+1)*gridPixelMultiplier,y=(Math.abs(dy)+1)*gridPixelMultiplier,m=!0,dx>0&&0==dy?v="horizontal":0==dx&&dy>0?v="vertical":dx>0&&dy>0?(v="diagonal-ne",f=gridPixelMultiplier):dx>0&&dy<0&&(v="diagonal-ne",y=gridPixelMultiplier);else{if(!h&&!s)return;"singleton"==h&&("singleton"==v?(e.strokeStyle="#000000",d<.5&&("rect-round"==mapStationStyle||u&&"rect-round"==u.style)&&(e.fillStyle=p)):o||(f=gridPixelMultiplier/2))}function b(a,o){if(e.save(),e.translate(t*gridPixelMultiplier,i*gridPixelMultiplier),e.rotate(o),m&&f>y){var l=f/gridPixelMultiplier;f+=l<4?l*(gridPixelMultiplier/4):l*(gridPixelMultiplier/3)}else if(m&&y>f){l=y/gridPixelMultiplier;y+=l<4?l*(gridPixelMultiplier/4):l*(gridPixelMultiplier/3)}MMMDEBUG&&t==$("#station-coordinates-x").val()&&i==$("#station-coordinates-y").val()&&console.log(`stationSpan: ${l}, w1: ${f} h1: ${y}`),n?primitiveRoundRect(e,...a,f,y,n):(e.strokeRect(...a,f,y),e.fillRect(...a,f,y)),e.restore()}if(m&&!s&&(e.strokeStyle="#000000",e.fillStyle="#ffffff"),(!m||!u&&s)&&("rect-round"==mapStationStyle||u&&"rect-round"==u.style)&&(n=2),u&&"rect"==u.style&&(n=!1),e.lineWidth=o?gridPixelMultiplier/2:gridPixelMultiplier/4,s&&!1===h&&(f>y&&(f=y),m=!0),"conflicting"==h&&(v="singleton",f>y&&(f=y),m=!1),l&&(e.strokeStyle=l),r&&(e.fillStyle=r),MMMDEBUG&&t==$("#station-coordinates-x").val()&&i==$("#station-coordinates-y").val()&&console.log(`xy: ${t},${i}; wh: ${f},${y} xf: ${o} ld: ${v} ra: ${g} cs: ${h} dac: ${m} sC: ${l} fC: ${r}`),!m&&("circles-thin"==mapStationStyle&&u&&!u.style||u&&"circles-thin"==u.style))return s||(l="#000000",r="#ffffff"),void drawCircleStation(e,t,i,activeMap,o,.5,e.lineWidth,r,l);if("singleton"==v||!u&&s&&("horizontal"==v||"vertical"==v))g=[(t-.5)*gridPixelMultiplier,(i-.5)*gridPixelMultiplier,f,y];else if("horizontal"==v&&(m||o))g=[(t-.5)*gridPixelMultiplier,(i-.5)*gridPixelMultiplier,f,y];else if("horizontal"!=v||m)if("vertical"==v&&(m||o))g=[(t-.5)*gridPixelMultiplier,(i-.5)*gridPixelMultiplier,f,y];else if("vertical"!=v||m){if("diagonal-ne"==v&&(m||o))return void b([-.5*gridPixelMultiplier,-.5*gridPixelMultiplier],Math.PI/-4);if("diagonal-ne"==v&&!m)return void b([-.25*gridPixelMultiplier,-.5*gridPixelMultiplier],Math.PI/-4);if("diagonal-se"==v&&(m||o))return void b([-.5*gridPixelMultiplier,-.5*gridPixelMultiplier],Math.PI/4);if("diagonal-se"==v&&!m)return void b([-.25*gridPixelMultiplier,-.5*gridPixelMultiplier],Math.PI/4)}else g=[(t-.5)*gridPixelMultiplier,(i-.25)*gridPixelMultiplier,y,f];else g=[(t-.25)*gridPixelMultiplier,(i-.5)*gridPixelMultiplier,f,y];return n?primitiveRoundRect(e,...g,n):(e.strokeRect(...g),e.fillRect(...g)),m}function primitiveRoundRect(e,t,i,a,o,l){a<2*l&&(l=a/2),o<2*l&&(l=o/2),e.beginPath(),e.moveTo(t+l,i),e.arcTo(t+a,i,t+a,i+o,l),e.arcTo(t+a,i+o,t,i+o,l),e.arcTo(t,i+o,t,i,l),e.arcTo(t,i,t+a,i,l),e.stroke(),e.fill(),e.closePath()}function drawIndicator(e,t){var i=document.getElementById("metro-map-stations-canvas"),a=i.getContext("2d",{alpha:!1}),o=i.width/gridCols;if(getActiveLine(e,t,activeMap)){var l=getStation(e,t,activeMap),r=mapStationStyle;temporaryStation.style?r=temporaryStation.style:l&&l.style&&(r=l.style);var n=temporaryStation.transfer||l&&l.transfer;r&&"wmata"!=r&&"circles-lg"!=r?"circles-md"==r?drawCircleStation(a,e,t,activeMap,n,.3,o/2,"#00ff00","#000000"):"circles-sm"==r?drawCircleStation(a,e,t,activeMap,n,.25,o/4,"#00ff00","#000000"):"rect"==r?drawStyledStation_rectangles(a,e,t,activeMap,n,"#000000","#00ff00",!1,!0):"rect-round"!=r&&"circles-thin"!=r||drawStyledStation_rectangles(a,e,t,activeMap,n,"#000000","#00ff00",20,!0):drawStyledStation_WMATA(a,e,t,activeMap,n,"#000000","#00ff00")}}function drawLabel(e,t,i,a,o){var l=getLabel(t,i,a);if(l||o){o&&!l&&(l={text:"Label",shape:$("#label-shape").val(),"text-color":"#333333"});var r,n,s=e.measureText(l.text).width;r=s0?Math.floor(e/gridPixelMultiplier)*gridPixelMultiplier+gridPixelMultiplier:Math.floor(e/gridPixelMultiplier)*gridPixelMultiplier}function rgb2hex(e){if(/^#[0-9A-F]{6}$/i.test(e))return e;function t(e){return("0"+parseInt(e).toString(16)).slice(-2)}return"#"+t((e=e.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/))[1])+t(e[2])+t(e[3])}function saveMapHistory(e){mapHistory.length>MAX_UNDO_HISTORY&&mapHistory.shift(),JSON.stringify(e)!=window.localStorage.getItem("metroMap")&&(mapHistory.push(JSON.stringify(e)),$("span#undo-buffer-count").text("("+mapHistory.length+")"),$("#tool-undo").prop("disabled",!1)),debugUndoRedo()}function autoSave(e){"object"==typeof e&&(saveMapHistory(activeMap=e),e=JSON.stringify(e)),window.localStorage.setItem("metroMap",e),mapRedoHistory=[],$("#tool-redo").prop("disabled",!0),$("span#redo-buffer-count").text(""),menuIsCollapsed||($("#autosave-indicator").text("Saving locally ..."),$("#title").hide(),setTimeout((function(){$("#autosave-indicator").text(""),$("#title").show()}),1500))}function loadMapFromUndoRedo(e){e&&(window.localStorage.setItem("metroMap",e),$(".rail-line").remove(),loadMapFromObject(e=JSON.parse(e)),setMapSize(e,!0),drawCanvas(e),e.global&&e.global.style?(mapLineWidth=e.global.style.mapLineWidth||mapLineWidth||1,mapStationStyle=e.global.style.mapStationStyle||mapStationStyle||"wmata"):(mapLineWidth=1,mapStationStyle="wmata"),resetResizeButtons(gridCols),resetRailLineTooltips())}function undo(){var e=!1;if(mapHistory.length>1){var t=mapHistory.pop();e=mapHistory[mapHistory.length-1],$("span#undo-buffer-count").text("("+mapHistory.length+")")}else 1==mapHistory.length&&(e=mapHistory[0],$("span#undo-buffer-count").text(""));mapRedoHistory.length>MAX_UNDO_HISTORY&&mapRedoHistory.shift(),(t||e)!=mapRedoHistory[mapRedoHistory.length-1]&&(mapRedoHistory.push(t||e),$("#tool-redo").prop("disabled",!1),$("span#redo-buffer-count").text("("+mapRedoHistory.length+")"),debugUndoRedo(),loadMapFromUndoRedo(e),$(".tooltip").hide())}function redo(){var e=!1;mapRedoHistory.length>=1&&(e=mapRedoHistory.pop(),mapHistory.push(e),$("span#undo-buffer-count").text("("+mapHistory.length+")"),e?(0==mapRedoHistory.length?$("span#redo-buffer-count").text(""):$("span#redo-buffer-count").text("("+mapRedoHistory.length+")"),loadMapFromUndoRedo(e),$(".tooltip").hide()):$("span#redo-buffer-count").text(""))}function debugUndoRedo(){if(MMMDEBUG&&MMMDEBUG_UNDO){$("#announcement").html("");for(var e=0;e"+t+"")}}}function getURLParameter(e){return decodeURIComponent((new RegExp("[?|&]"+e+"=([^&;]+?)(&|#|;|$)").exec(location.search)||[null,""])[1].replace(/\+/g,"%20"))||null}function autoLoad(){if(gridStep=parseInt(window.localStorage.getItem("metroMapGridStep")||gridStep)||!1,"undefined"!=typeof savedMapData)activeMap=savedMapData;else{if(!window.localStorage.getItem("metroMap"))return $.get("/load/2LVHmJ3r").done((function(e){activeMap=e,"[ERROR]"==e.replace(/\s/g,"").slice(0,7)?(drawGrid(),bindRailLineEvents(),drawCanvas()):(setMapStyle(activeMap=JSON.parse(activeMap)),mapDataVersion=activeMap&&activeMap.global&&activeMap.global.data_version?activeMap.global.data_version:1,compatibilityModeIndicator(),mapSize=setMapSize(activeMap,mapDataVersion>1),loadMapFromObject(activeMap),mapHistory.push(JSON.stringify(activeMap)),setTimeout((function(){$("#tool-resize-"+gridRows).text("Initial size ("+gridRows+"x"+gridCols+")")}),1e3))})).fail((function(e){drawGrid(),bindRailLineEvents(),drawCanvas()})).always((function(){upgradeMapDataVersion(),setMapStyle(activeMap)})),void("undefined"!=typeof autoLoadError&&(autoLoadError+="Loading the default map.",$("#announcement").append('
'+autoLoadError+"
"),setTimeout((function(){$("#autoLoadError").remove()}),3e3)));activeMap=JSON.parse(window.localStorage.getItem("metroMap")),"undefined"!=typeof autoLoadError&&(autoLoadError+="Loading your last-edited map.")}setMapStyle(activeMap),mapDataVersion=activeMap&&activeMap.global&&activeMap.global.data_version?activeMap.global.data_version:1;var e=parseInt(getURLParameter("mapDataVersion"));if(MMMDEBUG&&e>=mapDataVersion)upgradeMapDataVersion(e);else try{upgradeMapDataVersion()}catch(e){console.warn("Error when trying to upgradeMapDataVersion(): "+e)}compatibilityModeIndicator(),mapSize=setMapSize(activeMap,mapDataVersion>1),loadMapFromObject(activeMap),mapHistory.push(JSON.stringify(activeMap)),"undefined"!=typeof autoLoadError&&($("#announcement").append('
'+autoLoadError+"
"),setTimeout((function(){$("#autoLoadError").remove()}),3e3)),setTimeout((function(){$("#tool-resize-"+gridRows).text("Initial size ("+gridRows+"x"+gridCols+")")}),1e3);var t=window.sessionStorage.getItem("zoomLevel");t&&resizeCanvas(t)}function getMapSize(e){var t=0;if("object"!=typeof e&&(e=JSON.parse(e)),3==mapDataVersion)for(var i in e.points_by_color)for(var a in e.points_by_color[i]){for(var o in thisColorHighestValue=Math.max(...Object.keys(e.points_by_color[i][a]).map(Number).filter(Number.isInteger).filter((function(t){return Object.keys(e.points_by_color[i][a][t]).length>0}))),e.points_by_color[i][a]){if(thisColorHighestValue>=ALLOWED_SIZES[ALLOWED_SIZES.length-2]){t=thisColorHighestValue;break}y=Math.max(...Object.keys(e.points_by_color[i][a][o]).map(Number).filter(Number.isInteger)),y>thisColorHighestValue&&(thisColorHighestValue=y)}thisColorHighestValue>t&&(t=thisColorHighestValue)}else if(2==mapDataVersion)for(var i in e.points_by_color){for(var o in thisColorHighestValue=Math.max(...Object.keys(e.points_by_color[i].xys).map(Number).filter(Number.isInteger).filter((function(t){return Object.keys(e.points_by_color[i].xys[t]).length>0}))),e.points_by_color[i].xys){if(thisColorHighestValue>=ALLOWED_SIZES[ALLOWED_SIZES.length-2]){t=thisColorHighestValue;break}y=Math.max(...Object.keys(e.points_by_color[i].xys[o]).map(Number).filter(Number.isInteger)),y>thisColorHighestValue&&(thisColorHighestValue=y)}thisColorHighestValue>t&&(t=thisColorHighestValue)}else if(1==mapDataVersion)for(var o in t=Math.max(...Object.keys(e).map(Number).filter(Number.isInteger).filter((function(t){return Object.keys(e[t]).length>0}))),e){if(t>=ALLOWED_SIZES[ALLOWED_SIZES.length-2])break;y=Math.max(...Object.keys(e[o]).map(Number).filter(Number.isInteger).filter((function(t){return Object.keys(e[o][t]).length>0}))),y>t&&(t=y)}return t}function setMapSize(e,t){var i=gridRows;if(t)gridRows=e.global.map_size,gridCols=e.global.map_size;else for(allowedSize of(highestValue=getMapSize(e),ALLOWED_SIZES))if(highestValue0?$("#tool-line-options button.original-rail-line").remove():e.global.lines={bd1038:{displayName:"Red Line"},df8600:{displayName:"Orange Line"},f0ce15:{displayName:"Yellow Line"},"00b251":{displayName:"Green Line"},"0896d7":{displayName:"Blue Line"},"662c90":{displayName:"Purple Line"},a2a2a2:{displayName:"Silver Line"},"000000":{displayName:"Logo"},"79bde9":{displayName:"Rivers"},cfe4a7:{displayName:"Parks"}};var i=1;for(var a in e.global.lines)e.global.lines.hasOwnProperty(a)&&null===document.getElementById("rail-line-"+a)&&(keyboardShortcut=i<11?' data-toggle="tooltip" title="Keyboard shortcut: '+numberKeys[i-1].replace("Digit","")+'"':i<21?' data-toggle="tooltip" title="Keyboard shortcut: Shift + '+numberKeys[i-1].replace("Digit","")+'"':i<31?' data-toggle="tooltip" title="Keyboard shortcut: Alt + '+numberKeys[i-1].replace("Digit","")+'"':"",$("#line-color-options fieldset").append('"),i++);$((function(){$('[data-toggle="tooltip"]').tooltip({container:"body"}),bindRailLineEvents(),resetRailLineTooltips(),$(".visible-xs").is(":visible")&&($("#canvas-container").removeClass("hidden-xs"),$("#tool-export-canvas").click(),$("#try-on-mobile").attr("disabled",!1))}))}}function updateMapObject(e,t,i,a){if(activeMap)var o=activeMap;else o=JSON.parse(window.localStorage.getItem("metroMap"));if(3==mapDataVersion&&"line"==activeTool?(o.points_by_color[a]||(o.points_by_color[a]={}),o.points_by_color[a][activeLineWidthStyle]||(o.points_by_color[a][activeLineWidthStyle]={})):2==mapDataVersion&&"line"==activeTool&&(o.points_by_color[a]&&o.points_by_color[a].xys||(o.points_by_color[a]={xys:{}})),"eraser"==activeTool){if(3==mapDataVersion){for(var l in a||(a=getActiveLine(e,t,o)),o.points_by_color[a])o.points_by_color&&o.points_by_color[a]&&o.points_by_color[a][l]&&o.points_by_color[a][l][e]&&o.points_by_color[a][l][e][t]&&delete o.points_by_color[a][l][e][t];o.stations&&o.stations[e]&&o.stations[e][t]&&delete o.stations[e][t],o.labels&&o.labels[e]&&o.labels[e][t]&&delete o.labels[e][t]}else 2==mapDataVersion?(a||(a=getActiveLine(e,t,o)),o.points_by_color&&o.points_by_color[a]&&o.points_by_color[a].xys&&o.points_by_color[a].xys[e]&&o.points_by_color[a].xys[e][t]&&delete o.points_by_color[a].xys[e][t],o.stations&&o.stations[e]&&o.stations[e][t]&&delete o.stations[e][t],o.labels&&o.labels[e]&&o.labels[e][t]&&delete o.labels[e][t]):1==mapDataVersion&&o[e]&&o[e][t]&&delete o[e][t];return o}if(1==mapDataVersion&&(o.hasOwnProperty(e)?o[e].hasOwnProperty(t)||(o[e][t]={}):(o[e]={},o[e][t]={})),3==mapDataVersion&&"line"==activeTool)for(var r in o.points_by_color[a][activeLineWidthStyle][e]||(o.points_by_color[a][activeLineWidthStyle][e]={}),o.points_by_color[a][activeLineWidthStyle][e][t]=1,o.points_by_color)for(var l in o.points_by_color[r])r==a&&l==activeLineWidthStyle||o.points_by_color[r][l]&&o.points_by_color[r][l][e]&&o.points_by_color[r][l][e][t]&&delete o.points_by_color[r][l][e][t];else if(2==mapDataVersion&&"line"==activeTool)for(var r in o.points_by_color[a].xys[e]||(o.points_by_color[a].xys[e]={}),o.points_by_color[a].xys[e][t]=1,o.points_by_color)r!=a&&o.points_by_color[r].xys&&o.points_by_color[r].xys[e]&&o.points_by_color[r].xys[e][t]&&delete o.points_by_color[r].xys[e][t];else 1==mapDataVersion&&"line"==activeTool?o[e][t].line=a:"station"==activeTool?2==mapDataVersion||3==mapDataVersion?(o.stations||(o.stations={}),o.stations[e]||(o.stations[e]={}),o.stations[e][t]||(o.stations[e][t]={}),o.stations[e][t][i]=a):o[e][t].station[i]=a:"label"==activeTool&&(2!=mapDataVersion&&3!=mapDataVersion||(o.labels||(o.labels={}),o.labels[e]||(o.labels[e]={}),o.labels[e][t]||(o.labels[e][t]={}),i?o.labels[e][t][i]=a:o.labels[e][t]=a));return o}function moveMap(e){if("station"!=activeTool||!$("#tool-station-options").is(":visible")){var t=0,i=0;if("left"==e)t=-1;else if("right"==e)t=1;else if("down"==e)i=1;else if("up"==e)i=-1;if(3==mapDataVersion){var a={},o={};for(var l in activeMap.points_by_color)for(var r in a[l]={},activeMap.points_by_color[l])for(var n in a[l][r]={},activeMap.points_by_color[l][r])for(var s in activeMap.points_by_color[l][r][n])if(n=parseInt(n),s=parseInt(s),Number.isInteger(n)&&Number.isInteger(s)){if(a[l][r][n+t]||(a[l][r][n+t]={}),0==n&&"left"==e)return;if(n==gridCols-1&&"right"==e)return;if(0==s&&"up"==e)return;if(s==gridRows-1&&"down"==e)return;0<=n&&n=gridCols&&(n=gridCols-1),s<0?s=0:s>=gridRows&&(s=gridRows-1),[n,s]}function ffSpan(e,t,i){return i?!(!e||!t||e[0]==t[0]&&e[1]==t[1])||(!(!e||t)||!(e||!t)):void 0===e&&void 0===t||!(!e||!t||e[0]!=t[0]||e[1]!=t[1])}function floodFill(e,t,i,a,o){if(!(mapDataVersion>=3&&i&&i[0]==a&&i[1]==activeLineWidthStyle)&&!(i==a&&mapDataVersion<3)&&e&&t){var l=!1,r=spanBelow=0,n=[e,t],s={};if(o){var c=document.getElementById("hover-canvas");c.getContext("2d").clearRect(0,0,c.width,c.height)}if(mapDataVersion<=2)for(;n.length>0;){for(t=n.pop(),l=e=n.pop();l>=0&&getActiveLine(l,t,activeMap,mapDataVersion>=3)==i;)l--;for(l++,r=spanBelow=0;l=3)==i;){if(o){if(s&&s[l]&&s[l][t])break;s.hasOwnProperty(l)||(s[l]={}),drawHoverIndicator(l,t,a,.5),s[l][t]=!0}else updateMapObject(l,t,"line",a);!r&&t>0&&getActiveLine(l,t-1,activeMap,mapDataVersion>=3)==i?(n.push(l,t-1),r=1):r&&t>0&&getActiveLine(l,t-1,activeMap,mapDataVersion>=3)!=i&&(r=0),!spanBelow&&t=3)==i?(n.push(l,t+1),spanBelow=1):spanBelow&&t=3)!=i&&(spanBelow=0),l++}}else for(;n.length>0;){for(t=n.pop(),l=e=n.pop();l>=0&&ffSpan(getActiveLine(l,t,activeMap,mapDataVersion>=3),i);)l--;for(l++,r=spanBelow=0;l=3),i);){if(o){if(s&&s[l]&&s[l][t])break;s.hasOwnProperty(l)||(s[l]={}),drawHoverIndicator(l,t,a,.5),s[l][t]=!0}else updateMapObject(l,t,"line",a);!r&&t>0&&ffSpan(getActiveLine(l,t-1,activeMap,mapDataVersion>=3),i)?(n.push(l,t-1),r=1):r&&t>0&&ffSpan(getActiveLine(l,t-1,activeMap,mapDataVersion>=3),i,!0)&&(r=0),!spanBelow&&t=3),i)?(n.push(l,t+1),spanBelow=1):spanBelow&&t=3),i,!0)&&(spanBelow=0),l++}}}}function combineCanvases(){drawCanvas(activeMap);var e=document.getElementById("metro-map-canvas"),t=document.getElementById("metro-map-stations-canvas");return e.getContext("2d",{alpha:!1}).drawImage(t,0,0),t.getContext("2d",{alpha:!0}).clearRect(0,0,t.width,t.height),e}function downloadImage(e,t){var i="metromapmaker.png";if(HTMLCanvasElement.prototype.toBlob)pngUrl&&URL.revokeObjectURL(pngUrl),e.toBlob((function(e){pngUrl=URL.createObjectURL(e),$("#metro-map-image-download-link").attr({download:i,href:pngUrl}),t?($("#grid-canvas").hide(),$("#hover-canvas").hide(),$("#ruler-canvas").hide(),$("#metro-map-canvas").hide(),$("#metro-map-stations-canvas").hide(),$("#metro-map-image").attr("src",pngUrl),$("#metro-map-image").show()):(document.getElementById("metro-map-image-download-link").click(),drawCanvas(activeMap))}));else{var a=e.toDataURL();$("#metro-map-image-download-link").attr({download:i,href:a}),t?($("#grid-canvas").hide(),$("#hover-canvas").hide(),$("#ruler-canvas").hide(),$("#metro-map-canvas").hide(),$("#metro-map-stations-canvas").hide(),$("#metro-map-image").attr("src",a),$("#metro-map-image").show()):(document.getElementById("metro-map-image-download-link").click(),drawCanvas(activeMap))}}function resetResizeButtons(e){$(".resize-grid").each((function(){if("Current"==$(this).html().split(" ")[0]){var e=$(this).attr("id").split("-").slice(2),t=e+"x"+e;$(this).text(t)}})),$("#tool-resize-"+e).text("Current size ("+e+"x"+e+")"),isMapStretchable(e)?($("#tool-resize-stretch").show(),$("#tool-resize-stretch").text("Stretch map to "+2*e+"x"+2*e)):$("#tool-resize-stretch").hide()}function throttle(e,t){let i=!0;return function(...a){i&&(i=!1,e.apply(this,a),setTimeout((()=>i=!0),t))}}function resetTooltipOrientation(){tooltipOrientation="",window.innerWidth<=768?tooltipOrientation="top":$("#snap-controls-right").is(":hidden")?tooltipOrientation="left":tooltipOrientation="right",$(".has-tooltip").each((function(){$(this).data("bs.tooltip")&&($(this).data("bs.tooltip").options.placement=tooltipOrientation)}))}function resetRailLineTooltips(){for(var e=$(".rail-line"),t="",i=0;i=3),indicatorColor,!0));else{$("#tool-line-icon-pencil").show(),$("#tool-eraser-icon-eraser").show(),$("#tool-line-icon-paint-bucket").hide(),$("#tool-line-caption-fill").hide(),menuIsCollapsed||($("#tool-line-caption-draw").show(),$("#tool-eraser-caption-eraser").show()),$("#tool-eraser-icon-paint-bucket").hide(),$("#tool-eraser-caption-fill").hide(),$("#tool-eraser-options").hide();var e=document.getElementById("hover-canvas");e.getContext("2d").clearRect(0,0,e.width,e.height),("line"==activeTool&&activeToolOption||"eraser"==activeTool)&&(indicatorColor="line"==activeTool&&activeToolOption?activeToolOption:"#ffffff",drawHoverIndicator(hoverX,hoverY,indicatorColor))}}function setURLAfterSave(e){window.location.href.split("=")[0]}function getSurroundingLine(e,t,i,a){return getActiveLine((e=parseInt(e))-1,t=parseInt(t),i)&&getActiveLine(e-1,t,i)==getActiveLine(e+1,t,i)?getActiveLine(e-1,t,i,a):getActiveLine(e,t-1,i)&&getActiveLine(e,t-1,i)==getActiveLine(e,t+1,i)?getActiveLine(e,t-1,i,a):getActiveLine(e-1,t-1,i)&&getActiveLine(e-1,t-1,i)==getActiveLine(e+1,t+1,i)?getActiveLine(e-1,t-1,i,a):getActiveLine(e-1,t+1,i)&&getActiveLine(e-1,t+1,i)==getActiveLine(e+1,t-1,i)?getActiveLine(e-1,t+1,i,a):void 0}function setAllStationOrientations(e,t){if(t=parseInt(t),-1!=ALLOWED_ORIENTATIONS.indexOf(t))if(2==mapDataVersion||3==mapDataVersion)for(var i in e.stations)for(var a in e.stations[i])i=parseInt(i),a=parseInt(a),e.stations[i][a].orientation=t;else if(1==mapDataVersion)for(var i in e)for(var a in e[i])i=parseInt(i),a=parseInt(a),Number.isInteger(i)&&Number.isInteger(a)&&-1!=Object.keys(e[i][a]).indexOf("station")&&(e[i][a].station.orientation=t)}function resetAllStationStyles(e){if(2==mapDataVersion||3==mapDataVersion)for(var t in e.stations)for(var i in e.stations[t])t=parseInt(t),i=parseInt(i),e.stations[t][i].style&&delete e.stations[t][i].style;else if(1==mapDataVersion)for(var t in e)for(var i in e[t])t=parseInt(t),i=parseInt(i),Number.isInteger(t)&&Number.isInteger(i)&&-1!=Object.keys(e[t][i]).indexOf("station")&&-1!=Object.keys(e[t][i].station).indexOf("style")&&delete e[t][i].station.style}function combineLineColorWidthStyle(e){return"object"==typeof e?e.join("-"):e}function getLineDirection(e,t,i){return e=parseInt(e),t=parseInt(t),origin=combineLineColorWidthStyle(getActiveLine(e,t,i,mapDataVersion>=3)),NW=combineLineColorWidthStyle(getActiveLine(e-1,t-1,i,mapDataVersion>=3)),NE=combineLineColorWidthStyle(getActiveLine(e+1,t-1,i,mapDataVersion>=3)),SW=combineLineColorWidthStyle(getActiveLine(e-1,t+1,i,mapDataVersion>=3)),SE=combineLineColorWidthStyle(getActiveLine(e+1,t+1,i,mapDataVersion>=3)),N=combineLineColorWidthStyle(getActiveLine(e,t-1,i,mapDataVersion>=3)),E=combineLineColorWidthStyle(getActiveLine(e+1,t,i,mapDataVersion>=3)),S=combineLineColorWidthStyle(getActiveLine(e,t+1,i,mapDataVersion>=3)),W=combineLineColorWidthStyle(getActiveLine(e-1,t,i,mapDataVersion>=3)),info={direction:!1,endcap:!1},origin?(origin==W&&W==E?info.direction="horizontal":origin==N&&N==S?info.direction="vertical":origin==NW&&NW==SE?info.direction="diagonal-se":origin==SW&&SW==NE?info.direction="diagonal-ne":origin==W||origin==E?(info.direction="horizontal",info.endcap=!0):origin==N||origin==S?(info.direction="vertical",info.endcap=!0):origin==NW||origin==SE?(info.direction="diagonal-se",info.endcap=!0):origin==SW||origin==NE?(info.direction="diagonal-ne",info.endcap=!0):info.direction="singleton",info):info}function getConnectedStations(e,t,i){if(getStation(e=parseInt(e),t=parseInt(t),i)){var a=getStation(e-1,t-1,i),o=getStation(e+1,t-1,i),l=getStation(e-1,t+1,i),r=getStation(e+1,t+1,i),n=getStation(e,t-1,i),s=getStation(e+1,t,i),c=getStation(e,t+1,i),p=getStation(e-1,t,i);if(!(a||o||l||r||n||s||c||p))return"singleton";var d={highest:{E:0,S:0,SE:0,NE:0},E:{},S:{},N:{},W:{},NE:{},SE:{},SW:{},NW:{}};if(s&&f(s)){var v=e,g=t;do{v+=1}while(f(getStation(v,g,i)));d.E={x1:v-1,y1:g},d.highest.E=v-e-1}if(c&&f(c)){v=e,g=t;do{g+=1}while(f(getStation(v,g,i)));d.S={x1:v,y1:g-1},d.highest.S=g-t-1}if(o&&f(o)){v=e,g=t;do{v+=1,g-=1}while(f(getStation(v,g,i)));d.NE={x1:v-1,y1:g+1},d.highest.NE=v-e-1}if(r&&f(r)){v=e,g=t;do{v+=1,g+=1}while(f(getStation(v,g,i)));d.SE={x1:v-1,y1:g-1},d.highest.SE=v-e-1}if(p&&f(p)){v=e,g=t;do{v-=1}while(f(getStation(v,g,i)));d.W={x1:v+1,y1:g},d.E.internal=!0,d.highest.E+=Math.abs(v-e)-1}if(n&&f(n)){v=e,g=t;do{g-=1}while(f(getStation(v,g,i)));d.N={x1:v,y1:g+1},d.S.internal=!0,d.highest.S+=Math.abs(g-t)-1}if(a&&f(a)){v=e,g=t;do{v-=1,g-=1}while(f(getStation(v,g,i)));d.NW={x1:v+1,y1:g+1},d.SE.internal=!0,d.highest.SE+=Math.abs(v-e)-1}if(l&&f(l)){v=e,g=t;do{v-=1,g+=1}while(f(getStation(v,g,i)));d.SW={x1:v+1,y1:g-1},d.NE.internal=!0,d.highest.NE+=Math.abs(v-e)-1}var m=Object.values(d.highest).filter((e=>e>0)),h=Math.max(...Object.values(m));if(0==m.length)return"singleton";if(m.indexOf(h)!=m.lastIndexOf(h))return"conflicting";var u=Object.keys(d.highest).filter((e=>!0!==Number.isNaN(e))).sort((function(e,t){return d.highest[e]-d.highest[t]})).reverse();return!u||!u[0]||!d[u[0]].internal&&{x0:e,y0:t,x1:d[u[0]].x1,y1:d[u[0]].y1}}function f(e){return!!e&&("rect"==e.style||"rect-round"==e.style||"circles-thin"==e.style||!(e.style||"rect"!=mapStationStyle&&"rect-round"!=mapStationStyle&&"circles-thin"!=mapStationStyle))}}function stretchMap(e){e||(e=activeMap);var t={};if(t.global=Object.assign({},activeMap.global),3==mapDataVersion){for(var i in t.points_by_color={},e.points_by_color)for(var a in t.points_by_color[i]={},e.points_by_color[i])for(var o in t.points_by_color[i][a]={},e.points_by_color[i][a])for(var l in e.points_by_color[i][a][o])o=parseInt(o),l=parseInt(l),e.points_by_color[i][a][o][l]&&(2*o>MAX_MAP_SIZE-1||2*l>MAX_MAP_SIZE-1||(t.points_by_color[i][a].hasOwnProperty(2*o)||(t.points_by_color[i][a][2*o]={}),t.points_by_color[i][a][2*o][2*l]=e.points_by_color[i][a][o][l]));for(var o in setMapSize(t),resetResizeButtons(gridCols),t.stations={},e.stations)for(var l in e.stations[o])o=parseInt(o),l=parseInt(l),2*o>=gridRows||2*l>=gridCols||(t.stations.hasOwnProperty(2*o)||(t.stations[2*o]={}),t.stations[2*o][2*l]=Object.assign({},e.stations[o][l]));for(o=1;oMAX_MAP_SIZE-1||2*l>MAX_MAP_SIZE-1||(t.points_by_color[i].xys.hasOwnProperty(2*o)||(t.points_by_color[i].xys[2*o]={}),t.points_by_color[i].xys[2*o][2*l]=e.points_by_color[i].xys[o][l]));for(var o in setMapSize(t),resetResizeButtons(gridCols),t.stations={},e.stations)for(var l in e.stations[o])o=parseInt(o),l=parseInt(l),2*o>=gridRows||2*l>=gridCols||(t.stations.hasOwnProperty(2*o)||(t.stations[2*o]={}),t.stations[2*o][2*l]=Object.assign({},e.stations[o][l]));for(o=1;o-1?$("#snap-controls-left").hide():$("#snap-controls-right").hide(),$("#toolbox button span.button-label").show(),$("#title, #remix, #credits, #rail-line-new, #rail-line-change, #rail-line-delete, #straight-line-assist-options, #flood-fill-options, #tool-move-all, #tool-resize-all").show(),mapDataVersion>=2&&$("#tool-map-style").show(),mapDataVersion>=3&&$("#line-style-options").show(),$("#tool-move-all, #tool-resize-all").removeClass("width-100"),$("#tool-flood-fill").prop("checked")?($("#tool-line-caption-draw").hide(),$("#tool-eraser-caption-eraser").hide()):($("#tool-line-caption-fill").hide(),$("#tool-eraser-caption-fill").hide()),1==$("#hide-save-share-url").length&&$("#hide-save-share-url").show(),"Name this map"==$("#name-this-map").text()&&$("#name-map, #name-this-map").show(),$("#controls-collapse-menu").show(),$("#controls-expand-menu").hide()}}function colorInUse(e){if(!activeMap||!activeMap.points_by_color||!activeMap.points_by_color[e])return!1;if(3==mapDataVersion){for(var t in activeMap.points_by_color[e])for(var i in activeMap.points_by_color[e][t])for(var a in activeMap.points_by_color[e][t][i])if(activeMap.points_by_color[e][t][i][a])return!0}else if(2==mapDataVersion){if(!activeMap.points_by_color[e].xys)return;for(var i in activeMap.points_by_color[e].xys)for(var a in activeMap.points_by_color[e].xys[i])if(activeMap.points_by_color[e].xys[i][a])return!0}}function cycleLineWidth(){var e=ALLOWED_LINE_WIDTHS.indexOf(100*activeLineWidth);-1==e||e==ALLOWED_LINE_WIDTHS.length-1?e=0:e+=1;var t=$('button[data-linewidth="'+ALLOWED_LINE_WIDTHS[e]+'"]');t.length||(t=$('button[data-linewidth="'+ALLOWED_LINE_WIDTHS[e]+'.0"]')),t.trigger("click")}function cycleLineStyle(){var e=ALLOWED_LINE_STYLES.indexOf(activeLineStyle);-1==e||e==ALLOWED_LINE_STYLES.length-1?e=0:e+=1,$('button[data-linestyle="'+ALLOWED_LINE_STYLES[e]+'"]').trigger("click")}function setLineStyle(e,t){var i;"solid"==e?(i=[],t.lineCap="round"):"dashed"==e?(i=[gridPixelMultiplier,1.5*gridPixelMultiplier],t.lineCap="square"):"dotted_dense"==e?(t.lineCap="butt",i=[gridPixelMultiplier/2,gridPixelMultiplier/4]):"dotted"==e?(t.lineCap="butt",i=[gridPixelMultiplier/2,gridPixelMultiplier/2]):"dense_thick"==e?(i=[2,2],t.lineCap="butt"):"dense_thin"==e?(i=[1,1],t.lineCap="butt"):(i=[],t.lineCap="round"),t.setLineDash(i)}function unfreezeMapControls(){window.innerWidth>768&&$("#tool-line").prop("disabled")&&$("#tool-export-canvas").click(),resetTooltipOrientation()}function drawRuler(e,t,i){var a=document.getElementById("ruler-canvas"),o=a.getContext("2d");o.globalAlpha=.33,o.fillStyle="#2ECC71",o.strokeStyle="#2ECC71";var l=a.width/gridCols;if(0==rulerOrigin.length)o.fillRect((e-.5)*l,(t-.5)*l,l,l);else if(rulerOrigin.length>0){o.clearRect(0,0,a.width,a.height),o.fillRect((rulerOrigin[0]-.5)*l,(rulerOrigin[1]-.5)*l,l,l),o.fillRect((e-.5)*l,(t-.5)*l,l,l),o.beginPath(),o.lineWidth=l,o.lineCap="round",o.moveTo(rulerOrigin[0]*l,rulerOrigin[1]*l),o.lineTo(e*l,t*l),o.stroke(),o.closePath(),o.textAlign="start",o.font="700 "+l+"px sans-serif",o.globalAlpha=.67,o.fillStyle="#000000";var r="",n=Math.abs(rulerOrigin[0]-e),s=Math.abs(rulerOrigin[1]-t);!n&&s||n&&!s?r+=n+s:n&&s&&(r+=n+", "+s),o.fillText(r,(e+1)*l,(t+1)*l)}i&&(rulerOrigin=[e,t])}function cycleGridStep(){var e=[3,5,7,!1],t=e.indexOf(gridStep);(gridStep=-1==t||t==e.length-1?e[0]:e[t+1])?showGrid():hideGrid(),window.localStorage.setItem("metroMapGridStep",gridStep),drawGrid()}function restyleAllLines(e,t,i){var a={points_by_color:{}};for(var o in a.stations=Object.assign({},activeMap.stations),a.global=Object.assign({},activeMap.global),activeMap.points_by_color)for(var l in activeMap.points_by_color[o]){if(mapDataVersion>=3)var r=l.split("-")[0],n=l.split("-")[1];var s=(e||r)+"-"+(t||n);a.points_by_color[o]||(a.points_by_color[o]={}),a.points_by_color[o][s]||(a.points_by_color[o][s]={}),a.points_by_color[o][s]=Object.assign(a.points_by_color[o][s],activeMap.points_by_color[o][l])}2==mapDataVersion&&(mapDataVersion=3,a.global.data_version=3,compatibilityModeIndicator()),activeMap=a,i||(autoSave(activeMap),drawCanvas(activeMap))}function upgradeMapDataVersion(e){if(1==mapDataVersion){if(e&&1==e)return $("#line-style-options").hide(),$("#tool-map-style, #tool-map-style-options").hide(),void $('#station-style, label[for="station-style"]').hide();var t={points_by_color:{},stations:{}};t.global=Object.assign({},activeMap.global);var i=getMapSize(activeMap)||0;for(allowedSize of ALLOWED_SIZES)if(i=0?i=numberKeys.indexOf(e.code)+10:!e.metaKey&&!e.ctrlKey&&e.altKey&&numberKeys.indexOf(e.code)>=0?i=numberKeys.indexOf(e.code)+20:!e.metaKey&&!e.ctrlKey&&numberKeys.indexOf(e.code)>=0&&(i=numberKeys.indexOf(e.code)):(e.preventDefault(),moveMap("right")):(e.preventDefault(),moveMap("left")):mapDataVersion>=3&&cycleLineStyle():mapDataVersion>=3&&cycleLineWidth():$("#tool-ruler").trigger("click"):!menuIsCollapsed&&mapDataVersion>1&&$("#tool-map-style").trigger("click"):menuIsCollapsed?$("#controls-expand-menu").trigger("click"):$("#controls-collapse-menu").trigger("click"),!1!==i&&t[i]&&t[i].click()}})),activeTool="look",$("#toolbox button:not(.rail-line)").on("click",(function(){$(".active").removeClass("active"),$(this).addClass("active"),"line"==activeTool?$("#tool-line").addClass("active"):"eraser"==activeTool?$("#tool-eraser").addClass("active"):"station"==activeTool&&$("#tool-station").addClass("active"),rulerOn||"tool-ruler"!=$(this).attr("id")||$(this).removeClass("active")})),$("#toolbox button.rail-line").on("click",(function(){$(".active").removeClass("active"),$("#tool-line").addClass("active")})),$("#tool-line").on("click",(function(){$(".active").removeClass("active"),$("#tool-line").addClass("active"),$(this).hasClass("draw-rail-line")&&activeToolOption?(activeTool="line",$(this).css({"background-color":activeToolOption})):"eraser"==activeTool&&activeToolOption?activeTool="line":"eraser"==activeTool&&(activeTool="look"),$("#tool-line-options").is(":visible")?($("#tool-line-options").hide(),$("#tool-new-line-options").hide(),$("#tool-station").hasClass("width-100")||$(this).removeClass("width-100"),$("#rail-line-new span").text("Add New Line"),$("#tool-new-line-options").hide()):($("#tool-line-options").show(),$(this).addClass("width-100")),$(".tooltip").hide()})),$("#tool-flood-fill").change((function(){setFloodFillUI()})),$("#rail-line-delete").click((function(){var e=$(".rail-line"),t=[],i=Object.assign({},activeMap);if("line"==activeTool&&(activeTool="look",$("#tool-line").attr("style",""),$("#tool-line").removeClass("active")),2==mapDataVersion||3==mapDataVersion)for(var a of e)colorInUse(a=a.id.slice(10,16))||(t.push($("#rail-line-"+a)),delete activeMap.global.lines[a]);else if(1==mapDataVersion){delete i.global,i=JSON.stringify(i);for(var o=0;o0){autoSave(activeMap);for(var l=0;l=2&&(activeMap.global.map_size=parseInt(size)),setMapSize(activeMap,!0),activeTool=e,autoSave(activeMap)})),$("#tool-resize-stretch").click((function(){stretchMap(),autoSave(activeMap)})),$("#tool-move-all").on("click",(function(){$("#tool-move-options").is(":visible")?($("#tool-move-options").hide(),$(this).removeClass("active"),$(this).removeClass("width-100")):($("#tool-move-options").show(),$(this).addClass("width-100")),$(".tooltip").hide()})),$("#tool-move-up").click((function(){moveMap("up")})),$("#tool-move-down").click((function(){moveMap("down")})),$("#tool-move-left").click((function(){moveMap("left")})),$("#tool-move-right").click((function(){moveMap("right")})),$("#tool-save-map").click((function(){activeTool="look";var e=JSON.stringify(activeMap);autoSave(e);csrftoken=getCookie("csrftoken"),$("#tool-save-map").prop("disabled","disabled"),$("#tool-save-map span").text("Saving ..."),setTimeout((function(){$("#tool-save-map").prop("disabled",!1)}),1e3),$.post("/save/",{metroMap:e,csrfmiddlewaretoken:csrftoken}).done((function(e){if(e.replace(/\n/g,"").indexOf("No points_by_color")>-1)$("#tool-save-options").html('
Sorry, there was a problem saving your map: '+e.slice(9)+"
"),console.log("[WARN] Problem was: "+e),$("#tool-save-options").show();else{menuIsCollapsed&&$("#controls-expand-menu").trigger("click");var t=(e=e.split(","))[0].replace(/\s/g,""),i=e[1].replace(/\s/g,""),a='
You can then share this URL with a friend - and they can remix your map without you losing your original! If you make changes to this map, click Save & Share again to get a new URL.
"),$("#tool-save-options").html(""),i&&o&&$("#user-given-map-name").val(o),i&&l&&$("#user-given-map-tags").val(l),$("#name-map").submit((function(e){e.preventDefault()})),$("#map-somewhere-else").click((function(){$("#name-map").show(),$("#name-this-map").show(),$(this).parent().hide(),$("#name-this-map").removeClass(),$("#name-this-map").addClass("styling-blueline width-100"),$("#name-this-map").text("Name this map")})),$("#name-this-map").click((function(e){$("#user-given-map-name").val($("#user-given-map-name").val().replaceAll("<","").replaceAll(">","").replaceAll('"',"").replaceAll("\\\\","").replace("&","&").replaceAll("&","&").replaceAll("/","-").replaceAll("'",""));var t=$("#name-map").serializeArray().reduce((function(e,t){return e[t.name]=t.value,e}),{});window.sessionStorage.setItem("userGivenMapName",$("#user-given-map-name").val()),window.sessionStorage.setItem("userGivenMapTags",$("#user-given-map-tags").val()),csrftoken=getCookie("csrftoken"),t.csrfmiddlewaretoken=csrftoken,$.post("/name/",t,(function(){$("#name-map").hide(),$("#name-this-map").text("Thanks!"),setTimeout((function(){$("#name-this-map").hide()}),500)}))})),i&&o&&l&&($("#user-given-map-name").show(),$("#user-given-map-tags").show(),$("#name-this-map").click(),$("#name-this-map").hide()),$("#tool-save-options").show(),$("#hide-save-share-url").click((function(){$("#tool-save-options").hide()}))}})).fail((function(e){if(400==e.status)var t="Sorry, your map could not be saved. Did you flood fill the whole map? Use flood fill with the eraser to erase and try again.";else if(500==e.status)t="Sorry, your map could not be saved right now. This may be a bug, and the admin has been notified.";else if(e.status>=502)t="Sorry, your map could not be saved right now. Metro Map Maker is currently undergoing routine maintenance including bugfixes and upgrades. Please try again in a few minutes.";$("#tool-save-options").html('
'+t+"
"),$("#tool-save-options").show()})).always((function(){setTimeout((function(){$("#tool-save-map span").text("Save & Share")}),350)})),$(".tooltip").hide()})),$("#tool-download-image").click((function(){activeTool="look",downloadImage(combineCanvases()),$(".tooltip").hide()})),$("#tool-export-canvas").click((function(){if(activeTool="look",drawCanvas(activeMap),$("#tool-station-options").hide(),$(".tooltip").hide(),$("#grid-canvas").is(":visible")){$("#grid-canvas").hide(),$("#hover-canvas").hide(),$("#ruler-canvas").hide(),$("#metro-map-canvas").hide(),$("#metro-map-stations-canvas").hide();var e=document.getElementById("metro-map-canvas"),t=document.getElementById("metro-map-stations-canvas");e.getContext("2d",{alpha:!1}).drawImage(t,0,0);var i=e.toDataURL();$("#metro-map-image").attr("src",i),$("#metro-map-image").show(),$("#export-canvas-help").show(),$("button:not(.mobile-browse)").attr("disabled",!0),$(this).attr("disabled",!1),$(this).attr("title","Go back to editing your map").tooltip("fixTitle").tooltip("show")}else $("#grid-canvas").show(),$("#hover-canvas").show(),$("#ruler-canvas").show(),$("#metro-map-canvas").show(),$("#metro-map-stations-canvas").show(),$("#metro-map-image").hide(),$("#export-canvas-help").hide(),$("button").attr("disabled",!1),$(this).attr("title","Download your map to share with friends").tooltip("fixTitle").tooltip("show")})),$("#tool-clear-map").click((function(){gridRows=80,gridCols=80,(activeMap={global:Object.assign({},activeMap.global),points_by_color:{},stations:{}}).global.map_size=80,snapCanvasToGrid(),drawGrid(),lastStrokeStyle=void 0,drawCanvas(activeMap,!1,!0),drawCanvas(activeMap,!0,!0),window.sessionStorage.removeItem("userGivenMapName"),window.sessionStorage.removeItem("userGivenMapTags"),$(".resize-grid").each((function(){var e=$(this).attr("id").split("-").slice(2),t=e+" x "+e;e==ALLOWED_SIZES[0]&&(t+=" (Current size)"),$(this).html(t)})),showGrid(),$(".tooltip").hide()})),$("#rail-line-new").click((function(){$("#tool-new-line-options").is(":visible")?($(this).children("span").text("Add New Line"),$("#tool-new-line-options").hide()):($(this).children("span").text("Hide Add Line options"),$("#tool-new-line-options").show())})),$("#create-new-rail-line").click((function(){$("#new-rail-line-name").val($("#new-rail-line-name").val().replaceAll("<","").replaceAll(">","").replaceAll('"',"").replaceAll("\\\\","").replaceAll("/","-"));var e=[],t=[];for(var i in $(".rail-line").each((function(){e.push($(this).attr("id").slice(10,16)),t.push($(this).text())})),""==$("#new-rail-line-color").val()&&$("#new-rail-line-color").val("#000000"),e.indexOf($("#new-rail-line-color").val().slice(1,7))>=0?$("#tool-new-line-errors").text("This color already exists! Please choose a new color."):t.indexOf($("#new-rail-line-name").val())>=0?$("#tool-new-line-errors").text("This rail line name already exists! Please choose a new name."):0==$("#new-rail-line-name").val().length?$("#tool-new-line-errors").text("This rail line name cannot be blank. Please enter a name."):$("#new-rail-line-name").val().length>100?$("#tool-new-line-errors").text("This rail line name is too long. Please shorten it."):$(".rail-line").length>99?$("#tool-new-line-errors").text("Too many rail lines! Delete your unused ones before creating new ones."):($("#tool-new-line-errors").text(""),$("#line-color-options fieldset").append('"),activeMap.global||(activeMap.global={lines:{}}),activeMap.global.lines||(activeMap.global.lines={}),$(".rail-line").each((function(){"rail-line-new"!=$(this).attr("id")&&(activeMap.global.lines[$(this).attr("id").slice(10,16)]={displayName:$(this).text()})})),autoSave(activeMap)),bindRailLineEvents(),resetRailLineTooltips(),$("#tool-lines-to-change").html(""),activeMap.global.lines)$("#tool-lines-to-change").append('")})),$("#rail-line-change").click((function(){for(var e in $("#tool-change-line-options").is(":visible")?($(this).children("span").html("Edit colors & names"),$("#tool-change-line-options").hide()):($(this).children("span").text("Close Edit Line options"),$("#tool-change-line-options").show()),$("#tool-lines-to-change").html(""),$("#change-line-name").hide(),$("#change-line-color").hide(),$("#tool-change-line-options label").hide(),$("#tool-change-line-options p").text(""),activeMap.global.lines)$("#tool-lines-to-change").append('")})),$("#tool-lines-to-change").on("change",(function(){$("#tool-change-line-options label").show(),"Edit which rail line?"!=$("#tool-lines-to-change option:selected").text()?($("#change-line-name").show(),$("#change-line-color").show(),$("#change-line-name").val($("#tool-lines-to-change option:selected").text()),$("#change-line-color").val("#"+$(this).val())):($("#tool-change-line-options p").text(""),$("#change-line-name").hide(),$("#change-line-color").hide())})),$("#save-rail-line-edits").click((function(){if("Edit which rail line?"!=$("#tool-lines-to-change option:selected").text()){var e=$("#tool-lines-to-change").val(),t=$("#change-line-color").val().slice(1),i=$("#tool-lines-to-change option:selected").text(),a=$("#change-line-name").val().replaceAll("<","").replaceAll(">","").replaceAll('"',"").replaceAll("\\\\","").replaceAll("/","-"),o=[];$(".rail-line").each((function(){o.push($(this).text())})),e!=t&&Object.keys(activeMap.global.lines).indexOf(t)>=0?$("#cant-save-rail-line-edits").text("Can't change "+i+" - it has the same color as "+activeMap.global.lines[t].displayName):o.indexOf(a)>-1&&i!=a?$("#cant-save-rail-line-edits").text("This rail line name already exists! Please choose a new name."):(replaceColors({color:e,name:i},{color:t,name:a}),$("#rail-line-change span").html("Edit colors & names"),$("#cant-save-rail-line-edits").text(""),$("#tool-change-line-options").hide(),"line"==activeTool&&(activeTool="look",$("#tool-line").attr("style",""),$("#tool-line").removeClass("active")))}"line"==activeTool&&rgb2hex(activeToolOption).slice(1,7)==e&&(activeToolOption="#"+t),resetRailLineTooltips()})),$("#tool-map-style").on("click",(function(){$("#tool-map-style-options").toggle(),$("#tool-map-style-options").is(":visible")||$("#tool-map-style").removeClass("active"),$(".tooltip").hide(),.75==mapLineWidth?$("#tool-map-style-line-750").addClass("active-mapstyle"):$("#tool-map-style-line-"+1e3*mapLineWidth).addClass("active-mapstyle"),$("#tool-map-style-station-"+mapStationStyle).addClass("active-mapstyle")})),$(".map-style-line").on("click",(function(){mapLineWidth="tool-map-style-line-750"==$(this).attr("id")?.75:1/parseInt($(this).data("line-width-divisor")),$(".map-style-line.active-mapstyle").removeClass("active-mapstyle"),$(this).addClass("active-mapstyle"),activeMap&&activeMap.global&&activeMap.global.style?activeMap.global.style.mapLineWidth=mapLineWidth:activeMap&&activeMap.global&&(activeMap.global.style={mapLineWidth:mapLineWidth}),mapDataVersion>=3?restyleAllLines(mapLineWidth):drawCanvas(activeMap),autoSave(activeMap)})),$(".map-style-station").on("click",(function(){mapStationStyle=$(this).data("station-style"),$(".map-style-station.active-mapstyle").removeClass("active-mapstyle"),$(this).addClass("active-mapstyle"),$("#reset-all-station-styles").text("Set ALL stations to "+$(this).text()),activeMap&&activeMap.global&&activeMap.global.style?activeMap.global.style.mapStationStyle=mapStationStyle:activeMap&&activeMap.global&&(activeMap.global.style={mapStationStyle:mapStationStyle}),autoSave(activeMap),drawCanvas()})),$("#station-name").change((function(){$(this).val($(this).val().replaceAll('"',"").replaceAll("'","").replaceAll("<","").replaceAll(">","").replaceAll("&","").replaceAll("/","").replaceAll("_"," ").replaceAll("\\\\","").replaceAll("%",""));var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val();Object.keys(temporaryStation).length>0&&(2==mapDataVersion||3==mapDataVersion?(activeMap.stations||(activeMap.stations={}),activeMap.stations[e]||(activeMap.stations[e]={}),activeMap.stations[e][t]=Object.assign({},temporaryStation)):activeMap[e][t].station=Object.assign({},temporaryStation),temporaryStation={}),metroMap=updateMapObject(e,t,"name",$("#station-name").val()),autoSave(metroMap),drawCanvas(metroMap,!0)})),$("#station-name-orientation").change((function(){var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val(),i=parseInt($(this).val());e>=0&&t>=0&&(0==i?Object.keys(temporaryStation).length>0?temporaryStation.orientation=0:2==mapDataVersion||3==mapDataVersion?activeMap.stations[e][t].orientation=0:1==mapDataVersion&&(activeMap[e][t].station.orientation=0):ALLOWED_ORIENTATIONS.indexOf(i)>=0&&(Object.keys(temporaryStation).length>0?temporaryStation.orientation=i:2==mapDataVersion||3==mapDataVersion?activeMap.stations[e][t].orientation=i:1==mapDataVersion&&(activeMap[e][t].station.orientation=i))),window.localStorage.setItem("metroMapStationOrientation",i),0==Object.keys(temporaryStation).length&&autoSave(activeMap),drawCanvas(activeMap,!0),drawIndicator(e,t)})),$("#station-style").on("change",(function(){var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val(),i=$(this).val();e>=0&&t>=0&&(ALLOWED_STYLES.indexOf(i)>=0?Object.keys(temporaryStation).length>0?temporaryStation.style=i:2==mapDataVersion||3==mapDataVersion?activeMap.stations[e][t].style=i:1==mapDataVersion&&(activeMap[e][t].station.style=i):i||(2!=mapDataVersion&&3!=mapDataVersion||!activeMap.stations[e][t].style?1==mapDataVersion&&activeMap[e][t].station.style&&delete activeMap[e][t].station.style:delete activeMap.stations[e][t].style)),0==Object.keys(temporaryStation).length&&autoSave(activeMap),drawCanvas(activeMap,!0),drawIndicator(e,t)})),$("#station-transfer").click((function(){var e=$("#station-coordinates-x").val(),t=$("#station-coordinates-y").val();e>=0&&t>=0&&($(this).is(":checked")?Object.keys(temporaryStation).length>0?temporaryStation.transfer=1:2==mapDataVersion||3==mapDataVersion?activeMap.stations[e][t].transfer=1:1==mapDataVersion&&(activeMap[e][t].station.transfer=1):Object.keys(temporaryStation).length>0?delete temporaryStation.transfer:2==mapDataVersion||3==mapDataVersion?delete activeMap.stations[e][t].transfer:1==mapDataVersion&&delete activeMap[e][t].station.transfer),0==Object.keys(temporaryStation).length&&autoSave(activeMap),drawCanvas(activeMap,!0),drawIndicator(e,t)})),$("#loading").remove()})),$("#set-all-station-name-orientation").on("click",(function(){var e=$("#set-all-station-name-orientation-choice").val();setAllStationOrientations(activeMap,e),autoSave(activeMap),drawCanvas(),setTimeout((function(){$("#set-all-station-name-orientation").removeClass("active")}),500)})),$("#reset-all-station-styles").on("click",(function(){resetAllStationStyles(activeMap),autoSave(activeMap),drawCanvas(),setTimeout((function(){$("#reset-all-station-styles").removeClass("active")}),500)})),$("#try-on-mobile").click((function(){editOnSmallScreen()})),$("#i-am-on-desktop").on("click",(function(){editOnSmallScreen(),$("#tool-export-canvas").remove(),$("#tool-download-image").removeClass("hidden-xs")})),$("#controls-collapse-menu").on("click",collapseToolbox),$("#controls-expand-menu").on("click",expandToolbox),$(".line-style-choice-width").on("click",(function(){$(".line-style-choice-width").removeClass("active"),$(".line-style-choice-width.active-mapstyle").removeClass("active-mapstyle"),$(this).addClass("active-mapstyle"),$(this).addClass("active"),activeLineWidth=$(this).attr("data-linewidth")/100,activeLineWidthStyle=activeLineWidth+"-"+activeLineStyle,activeToolOption&&(activeTool="line")})),$(".line-style-choice-style").on("click",(function(){$(".line-style-choice-style").removeClass("active"),$(".line-style-choice-style.active-mapstyle").removeClass("active-mapstyle"),$(this).addClass("active-mapstyle"),$(this).addClass("active"),activeLineStyle=$(this).attr("data-linestyle"),activeLineWidthStyle=activeLineWidth+"-"+activeLineStyle,activeToolOption&&(activeTool="line")})),$("#label-text, #label-shape, #label-text-color, #label-bg-color-transparent, #label-bg-color").on("change",(function(){var e=$("#label-coordinates-x").val(),t=$("#label-coordinates-y").val();temporaryLabel.text=$("#label-text").val(),temporaryLabel.shape=$("#label-shape").val(),temporaryLabel["text-color"]=$("#label-text-color").val(),$("#label-bg-color-transparent").is(":checked")?temporaryLabel["bg-color"]=void 0:temporaryLabel["bg-color"]=$("#label-bg-color").val(),autoSave(activeMap=updateMapObject(e,t,!1,Object.assign({},temporaryLabel))),temporaryLabel={},drawCanvas(activeMap,!0),drawLabelIndicator(e,t)})),$("#tool-ruler").on("click",(function(){rulerOn=!rulerOn,rulerOrigin=[];var e=document.getElementById("ruler-canvas");e.getContext("2d").clearRect(0,0,e.width,e.height)})),$("#tool-undo").on("click",undo),$("#tool-redo").on("click",redo);
\ No newline at end of file
diff --git a/metro_map_saver/map_saver/templates/index.html b/metro_map_saver/map_saver/templates/index.html
index 4759a708..4a1260ed 100644
--- a/metro_map_saver/map_saver/templates/index.html
+++ b/metro_map_saver/map_saver/templates/index.html
@@ -124,8 +124,10 @@
This works best on desk
+
+
@@ -157,10 +159,31 @@
+
+
+
+
+
+
+
+
+
diff --git a/metro_map_saver/map_saver/templatetags/metromap_utils.py b/metro_map_saver/map_saver/templatetags/metromap_utils.py
index 76dcee4f..f0534c3c 100644
--- a/metro_map_saver/map_saver/templatetags/metromap_utils.py
+++ b/metro_map_saver/map_saver/templatetags/metromap_utils.py
@@ -5,6 +5,7 @@
from map_saver.validator import (
ALLOWED_ORIENTATIONS,
+ ALLOWED_LINE_STYLES,
ALLOWED_STATION_STYLES,
ALLOWED_CONNECTING_STATIONS,
is_hex,
@@ -26,7 +27,7 @@
]
@register.simple_tag
-def station_marker(station, default_shape, line_size, points_by_color, stations):
+def station_marker(station, default_shape, line_size, points_by_color, stations, data_version):
""" Generate the SVG shape for a station based on
whether it's a transfer station and what its shape is.
@@ -75,7 +76,16 @@ def station_marker(station, default_shape, line_size, points_by_color, stations)
else:
radius = False
- line_direction = get_line_direction(x, y, color, points_by_color)
+ # What is the line width/style for this line?
+ if data_version >= 3:
+ line_width_style = station['line_width_style']
+ line_size, line_style = line_width_style.split('-')
+ line_size = float(line_size)
+ else:
+ # line_width and style are set globally in data_version 2
+ line_width_style = None
+
+ line_direction = get_line_direction(x, y, color, points_by_color, line_width_style)
station_direction = get_connected_stations(x, y, stations)
draw_as_connected = False
@@ -210,7 +220,7 @@ def svg_rect(x, y, w, h, x_offset, y_offset, fill, stroke=None, stroke_width=0.2
return f''
return f''
-def get_line_direction(x, y, color, points_by_color):
+def get_line_direction(x, y, color, points_by_color, line_width_style=None):
""" Returns which direction this line is going in,
to help draw the positioning of rectangle stations
@@ -218,14 +228,21 @@ def get_line_direction(x, y, color, points_by_color):
color = color.removeprefix('#')
- NW = (x-1, y-1) in points_by_color[color]['xy']
- NE = (x+1, y-1) in points_by_color[color]['xy']
- SW = (x-1, y+1) in points_by_color[color]['xy']
- SE = (x+1, y+1) in points_by_color[color]['xy']
- N = (x, y-1) in points_by_color[color]['xy']
- E = (x+1, y) in points_by_color[color]['xy']
- S = (x, y+1) in points_by_color[color]['xy']
- W = (x-1, y) in points_by_color[color]['xy']
+ if not line_width_style:
+ # 'xy' isn't a valid value for a line width/style;
+ # but it's a compatibility measure for data_version == 2,
+ # which used that key in the same position as data_version == 3's
+ # line width and style.
+ line_width_style = 'xy'
+
+ NW = (x-1, y-1) in points_by_color[color][line_width_style]
+ NE = (x+1, y-1) in points_by_color[color][line_width_style]
+ SW = (x-1, y+1) in points_by_color[color][line_width_style]
+ SE = (x+1, y+1) in points_by_color[color][line_width_style]
+ N = (x, y-1) in points_by_color[color][line_width_style]
+ E = (x+1, y) in points_by_color[color][line_width_style]
+ S = (x, y+1) in points_by_color[color][line_width_style]
+ W = (x-1, y) in points_by_color[color][line_width_style]
if W and E:
return 'horizontal'
@@ -459,6 +476,8 @@ def station_text(station):
else:
x_val = station['xy'][0] + 0.75
+ y_val = station['xy'][1]
+
if station['orientation'] == 0:
# Right
pass
@@ -492,8 +511,22 @@ def station_text(station):
transform = ''
else:
transform = f' transform="rotate({station["orientation"]} {station["xy"][0]}, {station["xy"][1]})"'
+ elif station['orientation'] == 1:
+ text_anchor = ' text-anchor="middle"'
+ x_val = station['xy'][0]
+ if station.get('transfer'):
+ y_val = y_val - 1.75
+ else:
+ y_val = y_val - 1.25
+ elif station['orientation'] == -1:
+ text_anchor = ' text-anchor="middle"'
+ x_val = station['xy'][0]
+ if station.get('transfer'):
+ y_val = y_val + 1.75
+ else:
+ y_val = y_val + 1.25
- text = f''''''
+ text = f''''''
return format_html(
'{}{}{}',
@@ -595,6 +628,66 @@ def get_station_styles_in_use(stations, default_shape, line_size):
mark_safe(svg),
)
+@register.simple_tag
+def get_line_width_styles_for_svg_style(shapes_by_color):
+
+ """ Given a set of shapes_by_color,
+ get the unique line widths and styles
+ """
+
+ widths = set()
+ styles = set()
+
+ for color in shapes_by_color:
+ for width_style in shapes_by_color[color]:
+ width, style = width_style.split('-')
+ widths.add(width)
+ styles.add(style)
+
+ css_styles = []
+ for width in widths:
+ if width in SVG_STYLES:
+ css_styles.append(f".{SVG_STYLES[width]['class']} {{ {SVG_STYLES[width]['style']} }}")
+
+ for style in styles:
+ if style in SVG_STYLES:
+ css_styles.append(f".{SVG_STYLES[style]['class']} {{ {SVG_STYLES[style]['style']} }}")
+
+ css_styles = ''.join(css_styles)
+
+ return format_html(
+ '{}',
+ mark_safe(css_styles),
+ )
+
+@register.simple_tag
+def get_line_class_from_width_style(width_style, line_size):
+
+ """ Given a width_style and line_size, return the appropriate CSS class(es)
+ necessary for this line (if any)
+ """
+
+ classes = []
+
+ width, style = width_style.split('-')
+ line_size = str(line_size)
+ if width == line_size:
+ pass # No class necessary; it's the default
+ elif width in SVG_STYLES:
+ classes.append(SVG_STYLES[width]['class'])
+
+ if style == ALLOWED_LINE_STYLES[0]:
+ pass # No class necessary; it's the default (solid)
+ elif style in SVG_STYLES:
+ classes.append(SVG_STYLES[style]['class'])
+
+ classes = ' '.join(classes)
+
+ return format_html(
+ '{}',
+ mark_safe(classes)
+ )
+
@register.filter
def square_root(value):
return sqrt(value)
@@ -632,6 +725,10 @@ def map_color(color, color_map):
return color_map[color]
+@register.filter
+def underscore_to_space(value):
+ return value.replace('_', ' ')
+
SVG_DEFS = {
'wmata': {
'wm-xf': [ # WMATA transfer
@@ -655,3 +752,15 @@ def map_color(color, color_map):
}
}
+SVG_STYLES = {
+ 'dashed': {"class": "l1", "style": "stroke-dasharray: 1 1.5; stroke-linecap: square;"},
+ 'dotted': {"class": "l2", "style": "stroke-dasharray: .5 .5; stroke-linecap: butt;"},
+ 'dotted_dense': {"class": "l3", "style": "stroke-dasharray: .5 .25; stroke-linecap: butt;"},
+ 'dense_thin': {"class": "l4", "style": "stroke-dasharray: .05 .05; stroke-linecap: butt;"},
+ 'dense_thick': {"class": "l5", "style": "stroke-dasharray: .1 .1; stroke-linecap: butt;"},
+ '1': {"class": "w1", "style": "stroke-width: 1;"},
+ '0.75': {"class": "w2", "style": "stroke-width: .75;"},
+ '0.5': {"class": "w3", "style": "stroke-width: .5;"},
+ '0.25': {"class": "w4", "style": "stroke-width: .25;"},
+ '0.125': {"class": "w5", "style": "stroke-width: .125;"},
+}
diff --git a/metro_map_saver/map_saver/validator.py b/metro_map_saver/map_saver/validator.py
index 404a0b5e..c8c2608c 100644
--- a/metro_map_saver/map_saver/validator.py
+++ b/metro_map_saver/map_saver/validator.py
@@ -13,11 +13,25 @@
MAX_MAP_SIZE = ALLOWED_MAP_SIZES[-1]
VALID_XY = [str(x) for x in range(MAX_MAP_SIZE)]
ALLOWED_LINE_WIDTHS = [1, 0.75, 0.5, 0.25, 0.125]
+ALLOWED_LINE_STYLES = ['solid', 'dashed', 'dense_thin', 'dense_thick', 'dotted_dense', 'dotted']
ALLOWED_STATION_STYLES = ['wmata', 'rect', 'rect-round', 'circles-lg', 'circles-md', 'circles-sm', 'circles-thin']
-ALLOWED_ORIENTATIONS = [0, 45, -45, 90, -90, 135, -135, 180]
+ALLOWED_ORIENTATIONS = [0, 45, -45, 90, -90, 135, -135, 180, 1, -1]
ALLOWED_CONNECTING_STATIONS = ['rect', 'rect-round', 'circles-thin']
ALLOWED_TAGS = ['real', 'speculative', 'unknown'] # TODO: change 'speculative' to 'fantasy' here and everywhere else, it's the more common usage
+# TODO: ALLOWED_LABEL DETAILS
+# ALLOWED_LABEL_SHAPES = []
+# ALLOWED_LABEL_TEXT_LENGTH = {}
+# ALLOWED_LABEL_TEXT_COLORS = []
+# ALLOWED_LABEL_BG_COLORS = []
+
+ALLOWED_LINE_WIDTH_STYLES = []
+for allowed_width in ALLOWED_LINE_WIDTHS:
+ for allowed_style in ALLOWED_LINE_STYLES:
+ # Yes, I can use this in an import;
+ # ALLOWED_LINE_WIDTH_STYLES has been populated
+ ALLOWED_LINE_WIDTH_STYLES.append(f'{allowed_width}-{allowed_style}')
+
def is_hex(string):
""" Determines whether a string is a hexademical string (0-9, a-f) or not
@@ -97,6 +111,250 @@ def get_map_size(highest_xy_seen):
return allowed_size
return ALLOWED_MAP_SIZES[-1]
+def validate_metro_map_v3(metro_map):
+
+ """ Validate the MetroMap object, allowing mixing and matching line widths/styles.
+
+ Main difference from v2 is points by color has an additional layer: the width+style,
+ and drops the xy/xys intermediate key
+ """
+
+ validated_metro_map = {
+ 'global': {
+ 'data_version': 3,
+ 'lines': {},
+ 'style': {},
+ },
+ 'points_by_color': {},
+ 'stations': {},
+ }
+
+ if not metro_map.get('points_by_color'):
+ raise ValidationError(f"[VALIDATIONFAILED] 3-01: No points_by_color")
+
+ if not isinstance(metro_map['points_by_color'], dict):
+ raise ValidationError(f"[VALIDATIONFAILED] 3-02: points_by_color must be dict, is: {type(metro_map['points_by_color']).__name__}")
+
+ # Infer missing lines in global from points_by_color
+ # It's not pretty, and the lines could fail to validate for other reasons, but it's graceful.
+ inferred_lines = False
+ if not metro_map.get('global') or not metro_map.get('global', {}).get('lines'):
+ inferred_lines = True
+ metro_map['global'] = {
+ 'lines': {
+ color: {'displayName': color}
+ for color in metro_map['points_by_color']
+ }
+ }
+ else:
+ if not isinstance(metro_map['global']['lines'], dict):
+ metro_map['global']['lines'] = {}
+ if set(metro_map['global']['lines'].keys()) != set(metro_map['points_by_color'].keys()):
+ for color in metro_map['points_by_color']:
+ if color in metro_map['global']['lines']:
+ continue
+ metro_map['global']['lines'][color] = {'displayName': color}
+
+ valid_lines = []
+ # Allow HTML color names to be used, but convert them to hex values
+ metro_map['global']['lines'] = {
+ html_color_name_fragments.get(line.strip()) or line: data
+ for line, data in
+ metro_map['global']['lines'].items()
+ }
+
+ invalid_lines = []
+
+ remove_lines = {}
+ for line in metro_map['global']['lines']:
+ if not is_hex(line):
+ # Allow malformed invalid lines to be skipped so the rest of the map will validate
+ invalid_lines.append(line)
+ if not len(line) == 6:
+ # We know it's hex by this point, we can fix length
+ if len(line) == 3:
+ new_line = ''.join([line[0] * 2, line[1] * 2, line[2] * 2])
+ elif len((line * 6)[:6]) <= 6:
+ new_line = (line * 6)[:6]
+ else:
+ new_line = line[:6]
+ remove_lines[new_line] = line
+
+ for new_line, line in remove_lines.items():
+ metro_map['global']['lines'][new_line] = metro_map['global']['lines'].pop(line)
+
+ for line in metro_map['global']['lines']:
+
+ if line in invalid_lines:
+ continue
+
+ # Transformations to the display name could result in a non-unique display name, but it doesn't actually matter.
+ display_name = metro_map['global']['lines'][line].get('displayName', 'Rail Line')
+ if not isinstance(display_name, str) or len(display_name) < 1:
+ display_name = 'Rail Line'
+ elif len(display_name) > 255:
+ display_name = display_name[:255]
+
+ valid_lines.append(line)
+ validated_metro_map['global']['lines'][line] = {
+ 'displayName': sanitize_string(display_name)
+ }
+
+ line_width = metro_map['global'].get('style', {}).get('mapLineWidth', 1)
+ if line_width not in ALLOWED_LINE_WIDTHS:
+ line_width = ALLOWED_LINE_WIDTHS[0]
+
+ line_style = metro_map['global'].get('style', {}).get('mapLineStyle', 'solid')
+ if line_style not in ALLOWED_LINE_STYLES:
+ line_style = ALLOWED_LINE_STYLES[0]
+
+ station_style = metro_map['global'].get('style', {}).get('mapStationStyle', 'wmata')
+ if station_style not in ALLOWED_STATION_STYLES:
+ station_style = ALLOWED_STATION_STYLES[0]
+
+ validated_metro_map['global']['style'] = {
+ 'mapLineWidth': line_width,
+ 'mapLineStyle': line_style,
+ 'mapStationStyle': station_style,
+ }
+
+ # Points by Color
+ all_points_seen = set() # Must confirm that stations exist on these points
+ points_skipped = []
+ highest_xy_seen = -1 # Because 0 is a point
+ valid_points_by_color = {}
+ for color in metro_map['points_by_color']:
+ if color not in validated_metro_map['global']['lines']:
+ points_skipped.append(f'Color {color} not in global')
+ continue
+
+ if not isinstance(metro_map['points_by_color'][color], dict):
+ points_skipped.append(f'BAD LINE WIDTH/STYLE at {color} (non-dict)')
+ continue
+
+ for line_width_style in metro_map['points_by_color'][color]:
+
+ if not isinstance(metro_map['points_by_color'][color][line_width_style], dict):
+ points_skipped.append(f'BAD COORDS at {color} for {line_width_style}')
+ continue
+
+ if line_width_style not in ALLOWED_LINE_WIDTH_STYLES:
+ points_skipped.append(f'BAD LINE WIDTH/STYLE at {color}: {line_width_style}')
+
+ for x in metro_map['points_by_color'][color][line_width_style]:
+ if not isinstance(metro_map['points_by_color'][color][line_width_style][x], dict):
+ points_skipped.append(f'BAD X at {color}: {x}')
+ continue
+
+ if not x.isdigit():
+ points_skipped.append(f'NONINT X at {color}: {x}')
+ continue
+
+ if int(x) < 0 or int(x) >= MAX_MAP_SIZE:
+ points_skipped.append(f'OOB X at {color}: {x}')
+ continue
+
+ for y in metro_map['points_by_color'][color][line_width_style][x]:
+
+ if not y.isdigit():
+ points_skipped.append(f'NONINT Y at {color}: {x},{y}')
+ continue
+
+ if int(y) < 0 or int(y) >= MAX_MAP_SIZE:
+ points_skipped.append(f'OOB Y at {color}: {x},{y}')
+ continue
+
+ if (x, y) in all_points_seen:
+ # Already seen in another color
+ points_skipped.append(f'SKIPPING {color} POINT {x},{y}, ALREADY SEEN')
+ continue
+
+ if metro_map['points_by_color'][color][line_width_style][x][y] == 1:
+ # Originally I'd considered setting the line width / style at the [x][y],
+ # but I think it's better recorded at points_by_color[color][line_width_style]
+ all_points_seen.add((x, y))
+
+ if int(x) > highest_xy_seen:
+ highest_xy_seen = int(x)
+
+ if int(y) > highest_xy_seen:
+ highest_xy_seen = int(y)
+
+ if not valid_points_by_color.get(color):
+ valid_points_by_color[color] = {line_width_style: {}}
+ elif not valid_points_by_color[color].get(line_width_style):
+ valid_points_by_color[color][line_width_style] = {}
+
+ if not valid_points_by_color[color][line_width_style].get(x):
+ valid_points_by_color[color][line_width_style][x] = {}
+
+ valid_points_by_color[color][line_width_style][x][y] = 1
+
+ validated_metro_map['points_by_color'] = valid_points_by_color
+ if points_skipped:
+ logger.warn(f'Points skipped: {len(points_skipped)} Details: {points_skipped}')
+
+ # Stations
+ stations_skipped = []
+ valid_stations = {}
+ if metro_map.get('stations') and isinstance(metro_map['stations'], dict):
+ for x in metro_map['stations']:
+ if not isinstance(metro_map['stations'][x], dict):
+ stations_skipped.append(f'STA BAD X: {x}')
+ continue
+ for y in metro_map['stations'][x]:
+ if not isinstance(metro_map['stations'][x][y], dict):
+ stations_skipped.append(f'STA BAD Y: {y}')
+ continue
+
+ if (x, y) not in all_points_seen:
+ stations_skipped.append(f'STA BAD POS: {x},{y}')
+ continue
+
+ station_name = sanitize_string_without_html_entities(metro_map['stations'][x][y].get('name', '_') or '_')
+ if len(station_name) < 1:
+ station_name = '_'
+ elif len(station_name) > 255:
+ station_name = station_name[:255]
+
+ station = {'name': station_name}
+
+ try:
+ station_orientation = int(metro_map['stations'][x][y].get('orientation', ALLOWED_ORIENTATIONS[0]))
+ except Exception:
+ station_orientation = ALLOWED_ORIENTATIONS[0]
+
+ if station_orientation not in ALLOWED_ORIENTATIONS:
+ station_orientation = ALLOWED_ORIENTATIONS[0]
+ station['orientation'] = station_orientation
+
+ station_style = metro_map['stations'][x][y].get('style')
+ if station_style and station_style in ALLOWED_STATION_STYLES:
+ station['style'] = station_style
+
+ if metro_map['stations'][x][y].get('transfer'):
+ station['transfer'] = 1
+
+ # This station is valid, add it
+ if not valid_stations.get(x):
+ valid_stations[x] = {}
+
+ valid_stations[x][y] = station
+ validated_metro_map['stations'] = valid_stations
+
+ if stations_skipped:
+ logger.warn(f'Stations skipped: {len(stations_skipped)} Details: {stations_skipped}')
+
+ # TODO: Add support for labels
+
+ if highest_xy_seen == -1:
+ raise ValidationError(f"[VALIDATIONFAILED] 3-00: This map has no points drawn. If this is in error, please contact the admin.")
+
+ validated_metro_map['global']['map_size'] = get_map_size(highest_xy_seen)
+
+ return validated_metro_map
+
+
def validate_metro_map_v2(metro_map):
""" Validate the MetroMap object, with a more compact, optimized data representation
diff --git a/metro_map_saver/map_saver/views.py b/metro_map_saver/map_saver/views.py
index 29ba7216..d697883c 100644
--- a/metro_map_saver/map_saver/views.py
+++ b/metro_map_saver/map_saver/views.py
@@ -49,6 +49,8 @@
validate_metro_map,
hex64,
ALLOWED_TAGS,
+ ALLOWED_LINE_WIDTHS,
+ ALLOWED_LINE_STYLES,
ALLOWED_MAP_SIZES,
)
from .common_cities import CITIES
@@ -115,6 +117,8 @@ def get(self, request, **kwargs):
context['today'] = timezone.now().date()
context['ALLOWED_MAP_SIZES'] = ALLOWED_MAP_SIZES
+ context['ALLOWED_LINE_WIDTHS'] = [w * 100 for w in ALLOWED_LINE_WIDTHS]
+ context['ALLOWED_LINE_STYLES'] = ALLOWED_LINE_STYLES
return render(request, self.template_name, context)
@@ -609,7 +613,7 @@ def post(self, request, **kwargs):
'stations': ','.join(stations),
'map_size': mapdata.get('global', {}).get('map_size', -1) or -1,
}
- if data_version == 2:
+ if data_version >= 2:
map_details['data'] = mapdata
else:
map_details['mapdata'] = json.dumps(mapdata)
@@ -617,8 +621,6 @@ def post(self, request, **kwargs):
saved_map = SavedMap.objects.create(**map_details)
context['saved_map'] = f'{urlhash},{naming_token}'
except MultipleObjectsReturned:
- # This should never happen, but it happened once
- # Perhaps this was due to a race condition?
context['saved_map'] = f'{urlhash},'
else:
# Anything that appears before the first colon will be internal-only;
diff --git a/metro_map_saver/summary/management/commands/summarize_city.py b/metro_map_saver/summary/management/commands/summarize_city.py
index 23f9bf07..e570a87a 100644
--- a/metro_map_saver/summary/management/commands/summarize_city.py
+++ b/metro_map_saver/summary/management/commands/summarize_city.py
@@ -38,6 +38,8 @@ def handle(self, *args, **kwargs):
# Find maps that don't have city set, but do have either a name or suggested_city
maps = SavedMap.objects.filter(city=None).exclude((Q(name='') & Q(suggested_city='')))
+ # TODO: this is all pretty slow and could be sped up quite a bit
+
if not alltime:
since = datetime.date.today() - datetime.timedelta(days=7)
maps = maps.filter(created_at__date__gte=since)
diff --git a/metro_map_saver/summary/views.py b/metro_map_saver/summary/views.py
index 805e547f..dcb3105c 100644
--- a/metro_map_saver/summary/views.py
+++ b/metro_map_saver/summary/views.py
@@ -265,7 +265,7 @@ def get_context_data(self, **kwargs):
return context
def post(self, request, *args, **kwargs):
- city = request.POST.get('city')
+ city = request.POST.get('city', '').replace('/', ' ')
if city:
return HttpResponseRedirect(reverse_lazy('city', args=(city, )))
else:
diff --git a/requirements.txt b/requirements.txt
index b566ac2f..f507acc9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
-asgiref==3.7.2
-Django==5.0.1
+asgiref==3.8.1
+Django==5.1.2
django-debug-toolbar==4.2.0
django-taggit==5.0.1
mysqlclient==2.2.1