// ==UserScript==
// @name Map-Making Shortcuts
// @namespace https://gf.qytechs.cn/users/1179204
// @description use shortcut to help you mapping on map-making app
// @version 1.2.6
// @license BSD
// @author KaKa
// @match *://map-making.app/maps/*
// @icon https://www.svgrepo.com/show/521871/switch.svg
// ==/UserScript==
(function() {
let editor,selections,currentIndex
let map,mapListener,isApplied=false,customZoom=0.17,isCustom=false,isASV,isYSV
let isDrawing = false;
let startX, startY, endX, endY;
let selectionBox;
let isShift = false;
function getEditor() {
map=unsafeWindow.map
if(!map)getMap()
editor = unsafeWindow.editor
const activeSelections = editor.selections;
const locations=unsafeWindow.locations
selections = activeSelections.length > 0 ? activeSelections.flatMap(selection => selection.locations) : locations;
}
function exportAsCsv(){
if(!selections)getEditor()
const csvContent = jsonToCSV(selections);
downloadCSV(csvContent);
}
function downloadCSV(csvContent, fileName = "output.csv") {
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', fileName);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
function getTagsForRow(item, maxTags) {
const tags = item.tags || [];
return Array.from({ length: maxTags }, (_, index) => tags[index] || '');
}
function getFormattedDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
if (isNaN(date.getTime())) return '';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
return `${year}-${month}`;
}
function getMaxTagCount(jsonData) {
let maxTags = 0;
jsonData.forEach(item => {
if (item.tags && item.tags.length > maxTags) {
maxTags = item.tags.length;
}
});
return maxTags;
}
function jsonToCSV(jsonData) {
const maxTags = getMaxTagCount(jsonData);
const tagHeaders = Array.from({ length: maxTags }, (_, i) => `tag${i + 1}`);
const headers = ["lat", "lng", "panoId", "heading", "pitch", "zoom", "date", ...tagHeaders];
const rows = jsonData.map(item => {
const lat = item.location.lat|| '';
const lng = item.location.lng|| '';
const panoId = item.panoId|| '';
const heading = item.heading|| '';
const pitch = item.pitch|| '';
const zoom = item.zoom || '';
const date = getFormattedDate(item.panoDate)||'';
const tags = getTagsForRow(item, maxTags);
return [
lat,
lng,
panoId,
heading,
pitch,
zoom,
date,
...tags
];
});
const csvContent = [headers, ...rows].map(row => row.join(",")).join("\n");
return csvContent;
}
function switchLoc(locs) {
if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
if (!currentIndex) {
currentIndex =1;
} else {
currentIndex +=1
if (currentIndex>locs.length){
currentIndex=1
}
}
editor.openLocation(locs[currentIndex-1]);
focusOnLoc(locs[currentIndex-1])
if(isCustom)setTimeout(function(){setZoom(customZoom)},100)
}
function rewindLoc(locs) {
if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
if (!currentIndex) {
currentIndex =1;
}
else {
currentIndex -=1
if (currentIndex<1) currentIndex=selections.length
}
editor.openLocation(locs[currentIndex-1]);
focusOnLoc(locs[currentIndex-1])
if(isCustom)setTimeout(function(){setZoom(customZoom)},100)
}
function focusOnLoc(loc){
map.setCenter(loc.location)
map.setZoom(17)
}
function deleteLoc(loc){
editor.closeAndDeleteLocation(loc)
}
function rewindSelections(loc){
currentIndex=1
editor.openLocation(selections[0])
if(isCustom)setTimeout(function(){setZoom(customZoom)},100)
}
function setZoom(z){
if(z<0)z=0
if(z>4)z=4
const svControl=unsafeWindow.streetView
svControl.setZoom(z)
}
function resetDefaultZoom(){
if(mapListener) return
mapListener=function(){
if(isCustom){
let intervalId=setInterval(function(){
editor = unsafeWindow.editor
if(editor.currentLocation){
setZoom(customZoom)
clearInterval(intervalId)}
},50);
}
}
map.addListener('click', mapListener);
}
function getTag(index){
const tags=unsafeWindow.editor.tags
const result = Object.keys(tags).find(tag => tags[tag].order === index - 1)
if(result){
return result.trim()}
}
function deleteTags() {
let selections = unsafeWindow.editor.selections;
while (selections.length > 0) {
const item = selections[0];
const tag = JSON.parse(item.key);
const tagName = tag.tagName;
const locations = item.locations;
editor.deleteTag(tagName, locations);
selections = unsafeWindow.editor.selections;
}
}
function customLayer(name,tileUrl,maxZoom,minZoom){
return new google.maps.ImageMapType({
getTileUrl: function(coord, zoom) {
return tileUrl
.replace('{z}', zoom)
.replace('{x}', coord.x)
.replace('{y}', coord.y);
},
tileSize: new google.maps.Size(256, 256),
name: name,
maxZoom:maxZoom,
minZoom:minZoom||1
});
}
function classicMap(){
var tileUrl = `https://mapsresources-pa.googleapis.com/v1/tiles?map_id=61449c20e7fc278b&version=15797339025669136861&pb=!1m7!8m6!1m3!1i{z}!2i{x}!3i{y}!2i9!3x1!2m2!1e0!2sm!3m7!2sen!3sus!5e1105!12m1!1e3!12m1!1e2!4e0!5m5!1e0!8m2!1e1!1e1!8i47083502!6m6!1e12!2i2!11e0!39b0!44e0!50e0`
const tileLayer=customLayer('google_labels_reest',tileUrl,20)
map.mapTypes.stack.layers[0]=tileLayer
map.setMapTypeId('stack')
}
function resetGulf(){
var tileUrl = `https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
if(JSON.parse(localStorage.getItem('mapBoldCountryBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A17%7Cs.e%3Ag.s%7Cp.w%3A2%7Cp.c%3A%23000000%2Cs.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
if(JSON.parse(localStorage.getItem('mapBoldSubdivisionBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A18%7Cs.e%3Ag.s%7Cp.w%3A3%2Cs.e%3Al%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aoff%215m1%215f1.350000023841858`
if(JSON.parse(localStorage.getItem('mapBoldSubdivisionBorders'))&&JSON.parse(localStorage.getItem('mapBoldCountryBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A17%7Cs.e%3Ag.s%7Cp.w%3A2%7Cp.c%3A%23000000%2Cs.t%3A18%7Cs.e%3Ag.s%7Cp.w%3A3%2Cs.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
const tileLayer=customLayer('google_labels_reest',tileUrl,20)
map.mapTypes.stack.layers[2]=tileLayer
map.setMapTypeId('stack')
}
function setHW(){
map.mapTypes.stack.layers.splice(2, 1)
const tileUrl = `https://maprastertile-drcn.dbankcdn.cn/display-service/v1/online-render/getTile/24.12.10.10/{z}/{x}/{y}/?language=zh&p=46&scale=2&mapType=ROADMAP&presetStyleId=standard&pattern=JPG&key=DAEDANitav6P7Q0lWzCzKkLErbrJG4kS1u%2FCpEe5ZyxW5u0nSkb40bJ%2BYAugRN03fhf0BszLS1rCrzAogRHDZkxaMrloaHPQGO6LNg==`
const tileLayer=customLayer('Petal_Maps',tileUrl,20)
map.mapTypes.stack.layers[0]=tileLayer
map.setMapTypeId('stack')
}
function setGD(){
const tileUrl = `https://t2.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
const tileLayer=customLayer('GaoDe_Terrain',tileUrl,20)
//map.mapTypes.stack.layers[0]=tileLayer
const tileUrl_ = `https://t2.tianditu.gov.cn/tbo_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=tbo&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
const tileLayer_=customLayer('GaoDe_Border',tileUrl_,20)
map.mapTypes.stack.layers[1]=tileLayer_
const _tileUrl = `https://t2.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
const _tileLayer=customLayer('GaoDe_Labels',_tileUrl,20)
//map.mapTypes.stack.layers[2]=_tileLayer
map.setMapTypeId('stack')
}
function setYandex(){
const svUrl=`https://core-stv-renderer.maps.yandex.net/2.x/tiles?l=stv&x={x}&y={y}&z={z}&scale=1&v=2025.04.04.20.13-1_25.03.31-4-24330`
const baseUrl=`https://core-renderer-tiles.maps.yandex.net/tiles?l=map&v=5.04.07-2~b:250311142430~ib:250404100358-24371&x={x}&y={y}&z={z}&scale=1&lang=en_US`
const svLayer=customLayer('Yandex_StreetView',svUrl,20,5)
const baseLayer=customLayer('Yandex_Maps',baseUrl,20,1)
map.mapTypes.stack.layers.splice(2, 0,svLayer)
map.mapTypes.stack.layers.splice(2, 0,baseLayer)
map.mapTypes.set("stack",map.mapTypes.stack.layers)
map.setMapTypeId('stack')
}
function setApple(){
const svUrl=`https://lookmap.eu.pythonanywhere.com/bluelines_raster_2x/{z}/{x}/{y}.png`
const svLayer=customLayer('Apple_StreetView',svUrl,16)
map.mapTypes.stack.layers.splice(2, 0,svLayer)
map.setMapTypeId('stack')
}
async function downloadTile(id,g) {
try {
const response = await fetch(`https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=apiv3&panoid=${id}&output=tile&x=${g==='Gen4'?18:16}&y=${g==='Gen4'?13:11}&zoom=5&nbt=1&fover=2`);
const imageBlob = await response.blob();
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const dataUrl = canvas.toDataURL('image/jpeg');
const link = document.createElement('a');
link.href = dataUrl;
link.download = id+'.jpg';
link.click();
};
img.src = URL.createObjectURL(imageBlob);
} catch (error) {
console.error('Error:', error);
}
}
function lon2tile(lng,zoom) {
return (lng+180)/360*Math.pow(2,zoom);
}
function lat2tile(lat,zoom){
return (1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom);
}
function lonLatToEpsg3395Tile(lng, lat, zoom) {
return [lon2tile(lng,zoom), lat2tile(lat,zoom)];
}
function getMap(){
let element = document.getElementsByClassName("map-embed")[0]
try{
const keys = Object.keys(element)
const key = keys.find(key => key.startsWith("__reactFiber$"))
const props = element[key]
map=props.pendingProps.children[1].props.children[1].props.map
window.map=map
}
catch(error){
console.error('Failed to get map')
}
}
let onKeyDown = async (e) => {
if (e.target.tagName === 'INPUT' || e.target.isContentEditable||!isApplied) {
return;
}
if(!editor)getEditor()
if (e.key === 'q' || e.key === 'Q') {
e.stopImmediatePropagation();
switchLoc(selections)
};
if (e.key === 'e' || e.key === 'E') {
e.stopImmediatePropagation();
rewindLoc(selections)
};
if (e.key === 'c' || e.key === 'C') {
e.stopImmediatePropagation();
deleteLoc(selections[currentIndex-1])
};
if (e.key === 'v' || e.key === 'V') {
e.stopImmediatePropagation();
editor.closeLocation(editor.currentLocation.updatedProps)
};
if (e.key === 'r' || e.key === 'R') {
e.stopImmediatePropagation();
rewindSelections()
};
if ((e.shiftKey)&&(e.key === 'd' || e.key === 'D')) {
exportAsCsv()
}
if ((e.shiftKey)&&(e.key === 'h' || e.key === 'H')) {
setHW()
}
if ((e.shiftKey)&&(e.key === 'm' || e.key === 'M')) {
resetGulf()
}
if ((e.shiftKey)&&(e.key === 'n' || e.key === 'N')) {
classicMap()
}
if ((e.shiftKey)&&(e.key === 'b' || e.key === 'B')) {
deleteTags()
}
/*if (e.key === 't' || e.key === 'T') {
e.stopImmediatePropagation();
getEditor()
const panos=[]
for (const loc of selections) {
const panoId=loc.panoId
var gen
if (loc.tags.includes('Gen4')) gen='Gen4'
if(panoId) panos.push({id:panoId,g:gen})
}
const downloadPromises = panos.map(pano => downloadTile(pano.id, pano.g));
await Promise.all(downloadPromises);
};*/
if (e.key === 'g' || e.key === 'G') {
e.stopImmediatePropagation();
resetDefaultZoom()
if(!isCustom) {
var input = prompt('please enter a zoom value(0-4):');
var parsedValue = parseFloat(input);
if (isNaN(parsedValue)) {
alert('The input is not a valid zoom! Please try again.');
isCustom=false
} else {
customZoom=parsedValue
isCustom=true
resetDefaultZoom()
alert('Custom zoom has been applied!');
}
}
else {
isCustom=false
alert('Zoom customizing is cancelled!')}
}
}
document.addEventListener("keydown", onKeyDown);
var shortCutButton = document.createElement('button');
shortCutButton.textContent = 'Shortcut Off';
shortCutButton.style.position = 'absolute';
shortCutButton.style.top = '8px';
shortCutButton.style.right = '700px';
shortCutButton.style.zIndex = '9999';
shortCutButton.style.borderRadius = "18px";
shortCutButton.style.padding = "5px 10px";
shortCutButton.style.border = "none";
shortCutButton.style.backgroundColor = "#4CAF50";
shortCutButton.style.color = "white";
shortCutButton.style.cursor = "pointer";
shortCutButton.addEventListener('click', function(){
if(isApplied){
isApplied=false
shortCutButton.style.border='none'
shortCutButton.textContent = 'Shortcut Off';
}
else {isApplied=true
shortCutButton.textContent = 'ShortCut On';
shortCutButton.style.border='2px solid #fff'}
});
document.body.appendChild(shortCutButton)
document.addEventListener('keydown', function(e) {
if (e.shiftKey ) {
isShift = true;
}
});
document.addEventListener('keyup', function(e) {
if (!e.shiftKey ) {
isShift = false;
}
});
document.addEventListener('mousedown', function(e) {
if (e.button === 0&&isShift) {
isDrawing = true;
startX = e.clientX;
startY = e.clientY;
document.body.style.userSelect = 'none'
selectionBox = document.createElement('div');
selectionBox.style.position = 'absolute';
selectionBox.style.border = '2px solid rgba(0, 128, 255, 0.7)';
selectionBox.style.backgroundColor = 'rgba(0, 128, 255, 0.2)';
document.body.appendChild(selectionBox);
}
});
document.addEventListener('mousemove', function(e) {
if (isDrawing) {
endX = e.clientX;
endY = e.clientY;
const width = Math.abs(endX - startX);
const height = Math.abs(endY - startY);
selectionBox.style.left = `${Math.min(startX, endX)}px`;
selectionBox.style.top = `${Math.min(startY, endY)}px`;
selectionBox.style.width = `${width}px`;
selectionBox.style.height = `${height}px`;
selectionBox.style.zIndex = '999999';
}
});
document.addEventListener('mouseup', function(e) {
if (isDrawing) {
isDrawing = false;
const rect = selectionBox.getBoundingClientRect();
document.body.removeChild(selectionBox);
const elements = document.querySelectorAll('ul.tag-list');
elements.forEach(element => {
const childrens = element.querySelectorAll('li.tag.has-button');
childrens.forEach(child => {
const childRect = child.getBoundingClientRect();
if (
childRect.top >= rect.top &&
childRect.left >= rect.left &&
childRect.bottom <= rect.bottom &&
childRect.right <= rect.right
) {
child.click();
document.body.style.userSelect = 'text';
}
});
});
}
});
})();