1+ javascript:( function ( ) {
2+ /* Style values */
3+ const googleMapsCustomMeasureDistanceStyle = `
4+ /*all * { cursor: crosshair; } */
5+
6+ button#custom-measure-btn {
7+ position: fixed;
8+ bottom: 150px;
9+ right: 20px;
10+ z-index: 20000;
11+ padding: 12px;
12+ background: #4285f4;
13+ color: white;
14+ border: none;
15+ border-radius: 8px;
16+ cursor: pointer;
17+ font-weight: bold;
18+ box-shadow: 0 2px 6px rgba(0,0,0,0.3);
19+ }
20+ ` ;
21+
22+ /************************************* SUPPORT FUNCTIONS *************************************/
23+ /* Add draw events to map canvas */
24+ const googleMapsCustomMeasureDistanceDraw = ( ) => {
25+ /* global variables for inserted script */
26+ const googleMapsCustomMeasureDistanceLabelEl = /* select current scale */
27+ document . getElementById ( 'U5ELMd' ) ;
28+ const googleMapsCustomMeasureDistanceBarEl = /* get scal in pixels */
29+ document . querySelector ( '.Ty7QWe' ) ;
30+
31+ /* notify update not found */
32+ if ( ! googleMapsCustomMeasureDistanceLabelEl || ! googleMapsCustomMeasureDistanceBarEl )
33+ return alert ( 'Scale bar not found!' ) ;
34+
35+ let getRatio = ( ) => /* get the ration of pixels to scale */
36+ parseInt ( googleMapsCustomMeasureDistanceLabelEl . innerText . match ( / \d + / ) [ 0 ] ) / parseInt ( googleMapsCustomMeasureDistanceBarEl . style . width ) ;
37+
38+ let getUnit = ( ) => /* get the digit value of scale */
39+ googleMapsCustomMeasureDistanceLabelEl . innerText . replace ( / [ 0 - 9 ] | \s / g, '' ) ;
40+
41+ if ( document . getElementById ( 'custom-measure-btn' ) ) return ; /* no duplicates */
42+
43+ let btn = /* button to start */
44+ document . createElement ( 'button' ) ;
45+
46+ /* prepare button */
47+ btn . id = 'custom-measure-btn' ; /* unique identifier */
48+ btn . innerText = '📏 Custom Measure' ; /* data */
49+ document . body . appendChild ( btn ) ; /* add to page */
50+
51+ /* prepare svg */
52+ let svg = /* create svg */
53+ document . createElementNS ( 'http://www.w3.org/2000/svg' , 'svg' ) ;
54+ svg . style . cssText = /* style inline*/
55+ 'position:fixed; top:0; left:0; width:100%; height:100%; z-index:19999; pointer-events:none; display:none;' ;
56+ document . body . appendChild ( svg ) ; /* add to page */
57+
58+ /* set switches */
59+ let isDrawingMode = false , activeNode = null , startPt = null ;
60+
61+ /* add click event to button */
62+ btn . onclick = ( e ) => {
63+ e . stopPropagation ( ) ; /* toggle */
64+
65+ /* select inserted style tag */
66+ let s = document . getElementById ( "googleMapsCustomMeasureDistanceStyle" ) ;
67+
68+ /* update global cursor style */
69+ s . innerText = /* determine status */
70+ isDrawingMode ?
71+ s . innerText . replace ( "/*all*/ * { cursor: crosshair; }" , "/*all * { cursor: crosshair; } */" ) : /* comment out */
72+ s . innerText . replace ( "/*all * { cursor: crosshair; } */" , "/*all*/ * { cursor: crosshair; }" ) ; /* uncomment */
73+
74+ isDrawingMode = /* determine status */
75+ ! isDrawingMode ;
76+
77+ /* config button per status */
78+ btn . style . background = /* toggle draw mode */
79+ isDrawingMode ? '#db4437' : '#4285f4' ;
80+
81+ btn . innerText = /* button text */
82+ isDrawingMode ? '🛑 Stop' : '📏 Draggable Measure' ;
83+
84+ svg . style . display = /* show or hide svg */
85+ isDrawingMode ? 'block' : 'none' ;
86+
87+ svg . style . pointerEvents = /* toggle pointer events */
88+ isDrawingMode ? 'all' : 'none' ;
89+ } ;
90+
91+ /* line functionality */
92+ var updateLine = ( group ) => {
93+ /* draw line and annotations */
94+ let p1 = group . childNodes [ 0 ] ; /* start vector */
95+ let p2 = group . childNodes [ 1 ] ; /* end vector */
96+ let line = group . childNodes [ 2 ] ; /* line & dimen. */
97+ let txt = group . childNodes [ 3 ] ; /* measurement */
98+
99+ /* listen for current mouse position */
100+ let x1 = + p1 . getAttribute ( 'cx' ) ; /* current x position */
101+ let y1 = + p1 . getAttribute ( 'cy' ) ; /* current y position */
102+ let x2 = + p2 . getAttribute ( 'cx' ) ; /* end x position */
103+ let y2 = + p2 . getAttribute ( 'cy' ) ; /* end y position */
104+
105+ /* draw the custom line */
106+ line . setAttribute ( 'x1' , x1 ) ;
107+ line . setAttribute ( 'y1' , y1 ) ;
108+ line . setAttribute ( 'x2' , x2 ) ;
109+ line . setAttribute ( 'y2' , y2 ) ;
110+
111+ /* get value of current scale */
112+ let dist = ( Math . sqrt ( ( x2 - x1 ) ** 2 + ( y2 - y1 ) ** 2 ) * getRatio ( ) ) . toFixed ( 2 ) ;
113+
114+ /* add annotations */
115+ txt . setAttribute ( 'x' , ( x1 + x2 ) / 2 + 10 ) ;
116+ txt . setAttribute ( 'y' , ( y1 + y2 ) / 2 ) ;
117+ txt . textContent = dist + ' ' + getUnit ( ) ;
118+ } ;
119+
120+ /* update position for lines */
121+ svg . onmousedown = ( e ) => {
122+ if ( e . target . tagName === 'circle' ) {
123+ activeNode = e . target ;
124+ } else if ( isDrawingMode ) {
125+ if ( ! startPt ) {
126+ startPt = { /* set starting point */
127+ x : e . clientX ,
128+ y : e . clientY
129+ } ;
130+ } else {
131+ let g = /* add svg g element */
132+ document . createElementNS ( 'http://www.w3.org/2000/svg' , 'g' ) ;
133+
134+ /* start and end vectors */
135+ let c1 = /* add start vector circle indicator */
136+ createCircle ( startPt . x , startPt . y ) ;
137+ let c2 = /* add end vector circle indicator */
138+ createCircle ( e . clientX , e . clientY ) ;
139+
140+ /* draw line betwwen vectors */
141+ let l = /* add svg line element */
142+ document . createElementNS ( 'http://www.w3.org/2000/svg' , 'line' ) ;
143+
144+ /* prepare line */
145+ l . setAttribute ( 'stroke' , '#4285f4' ) ;
146+ l . setAttribute ( 'stroke-width' , '4' ) ;
147+
148+ /* distance of line */
149+ let t = /* add svg text element */
150+ document . createElementNS ( 'http://www.w3.org/2000/svg' , 'text' ) ;
151+ /* inline style text */
152+ t . style . cssText = 'font:bold 16px sans-serif; paint-order:stroke; stroke:white; stroke-width:4px; pointer-events:none;' ;
153+
154+ /* create vector representative circles */
155+ g . append ( c1 , c2 , l , t ) ;
156+ svg . appendChild ( g ) ;
157+
158+ /* add circles at end of line*/
159+ updateLine ( g ) ;
160+
161+ /* toggle switch */
162+ startPt = null ;
163+ }
164+ }
165+ } ;
166+
167+ svg . onmousemove = ( e ) => {
168+ /* check if moving vector circle */
169+ if ( activeNode ) {
170+ activeNode . setAttribute ( 'cx' , e . clientX ) ;
171+ activeNode . setAttribute ( 'cy' , e . clientY ) ;
172+ updateLine ( activeNode . parentNode ) ;
173+ }
174+ } ;
175+
176+ /* toggle switch */
177+ window . onmouseup = ( ) => activeNode = null ;
178+
179+
180+ var createCircle = ( x , y ) => {
181+ let c = /* create the vector representative circle */
182+ document . createElementNS ( 'http://www.w3.org/2000/svg' , 'circle' ) ;
183+
184+ /* prepare circle */
185+ c . setAttribute ( 'cx' , x ) ;
186+ c . setAttribute ( 'cy' , y ) ;
187+ c . setAttribute ( 'r' , 8 ) ;
188+ c . setAttribute ( 'fill' , '#db4437' ) ;
189+ c . style . cursor = 'move' ;
190+ return c ;
191+ } ;
192+ } ;
193+
194+ function googleMapsCustomMearsureDistance ( ) {
195+ let style = document . createElement ( "style" ) ;
196+ let script = document . createElement ( 'script' ) ;
197+
198+ /* no duplicates */
199+ if ( document . getElementById ( "googleMapsCustomMeasureDistanceStyle" ) ) return ;
200+
201+ /* else */
202+ /* prepare style and script */
203+ style . setAttribute ( "id" , "googleMapsCustomMeasureDistanceStyle" ) ;
204+ style . innerText = /* use global variable */
205+ googleMapsCustomMeasureDistanceStyle ;
206+
207+ script . textContent = /* make self calling function */
208+ '(' + googleMapsCustomMeasureDistanceDraw + ')();' ;
209+
210+ /* add style and script to page */
211+ document . documentElement . prepend ( style ) ;
212+ document . documentElement . appendChild ( script ) ;
213+
214+ /* no need for script after it calls itself */
215+ script . remove ( ) ;
216+ }
217+ googleMapsCustomMearsureDistance ( ) ;
218+ } ) ( ) ;
0 commit comments