Skip to content

Commit a9d936c

Browse files
committed
Tentative “Map Zoomer” test
See Description.md for details
1 parent ca38b24 commit a9d936c

File tree

15 files changed

+1077
-0
lines changed

15 files changed

+1077
-0
lines changed

MotionMark/resources/debug-runner/tests.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,10 @@ Suites.push(new Suite("Dev suite",
463463
url: "dev/stories/stories.html",
464464
name: "Stories"
465465
},
466+
{
467+
url: "dev/map-zoomer/map-zoomer.html",
468+
name: "Map Zoomer"
469+
},
466470
{
467471
url: "dev/radial-chart/radial-chart.html",
468472
name: "Canvas Radial Chart"

MotionMark/resources/extensions.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ class Point {
247247
this.y = y;
248248
}
249249

250+
clone()
251+
{
252+
return new Point(this.x, this.y);
253+
}
254+
250255
// Used when the point object is used as a size object.
251256
get width()
252257
{
@@ -290,6 +295,21 @@ class Point {
290295
return new Point(this.x * other, this.y * other);
291296
return new Point(this.x * other.x, this.y * other.y);
292297
}
298+
299+
min(other)
300+
{
301+
return new Point(Math.min(this.x, other.x), Math.min(this.y, other.y));
302+
}
303+
304+
max(other)
305+
{
306+
return new Point(Math.max(this.x, other.x), Math.max(this.y, other.y));
307+
}
308+
309+
scale(factor)
310+
{
311+
return new Point(this.x * factor, this.y * factor);
312+
}
293313

294314
move(angle, velocity, timeDelta)
295315
{
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Map Zoomer
2+
3+
Goals
4+
-----
5+
6+
A test that replicates what an embedded MapBox-style map might do.
7+
8+
9+
Design
10+
------
11+
12+
Each element is an emebdded map. The map tiles are `<img>` inside a container with a `scale` transform.
13+
14+
There is an SVG path overlay representing a GPS track.
15+
16+
There are controls layered on top, and photo "placards".
17+
18+
19+
Features tested
20+
---------------
21+
22+
* text drawing
23+
* image drawing
24+
* lines, arcs, curves
25+
* clipping to a path
26+
* gradients
27+
28+
29+
Work per measued frame
30+
----------------------
31+
32+
Redraw after changing the scale transform on the map tile container, the SVG path, and when repositioning the photo placards.
33+
34+
35+
Remaining work
36+
--------------
37+
38+
* Royalty free assets
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
<!--
2+
Copyright (C) 2025 Apple Inc. All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions
6+
are met:
7+
1. Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
2. Redistributions in binary form must reproduce the above copyright
10+
notice, this list of conditions and the following disclaimer in the
11+
documentation and/or other materials provided with the distribution.
12+
13+
THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16+
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17+
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
THE POSSIBILITY OF SUCH DAMAGE.
24+
-->
25+
<!DOCTYPE html>
26+
<html>
27+
<head>
28+
<meta charset="utf-8">
29+
<link rel="stylesheet" type="text/css" href="../../resources/stage.css">
30+
<style>
31+
@property --photo-size {
32+
syntax: "<length>";
33+
inherits: true;
34+
initial-value: 50px;
35+
}
36+
37+
@property --arrow-size {
38+
syntax: "<length>";
39+
inherits: true;
40+
initial-value: 8px;
41+
}
42+
43+
#stage-canvas {
44+
width: 100%;
45+
height: 100%;
46+
}
47+
48+
#container {
49+
position: absolute;
50+
inset: 10px;
51+
border: 1px solid black;
52+
}
53+
54+
.map-container {
55+
position: absolute;
56+
width: 400px;
57+
height: 320px;
58+
outline: 1px solid black;
59+
overflow: clip;
60+
will-change: transform;
61+
}
62+
63+
.tiles-container, .track-container, .photos-container, .controls-overlay {
64+
position: absolute;
65+
inset: 0;
66+
overflow: clip;
67+
}
68+
69+
.controls-overlay {
70+
position: absolute;
71+
top: 0;
72+
left: 0;
73+
z-index: 1;
74+
}
75+
76+
.zoom-controls {
77+
position: absolute;
78+
left: 10px;
79+
top: 10px;
80+
width: 26px;
81+
height: 52px;
82+
background-color: white;
83+
border-radius: 2px;
84+
border: 2px solid rgba(0,0,0,0.2);
85+
background-clip: padding-box;
86+
}
87+
88+
.zoom-controls > a {
89+
display: block;
90+
height: 26px;
91+
text-decoration: none;
92+
color: rgba(0,0,0,0.75);
93+
background-position: 0 0;
94+
background-size: 26px 260px;
95+
background-repeat: no-repeat;
96+
opacity: 0.75;
97+
text-indent: -999em;
98+
/* this image needs editing */
99+
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAIICAMAAAAWgT0mAAAABGdBTUEAALGPC%2FxhBQAAAAFzUkdCAK7OHOkAAABgUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPpP6I0AAAAfdFJOUwDYoAgNNvSA4%2Fu3mcTrUb5VQCByjBcoRwJMrs9nfF29SYbmAAAFH0lEQVR42u2c55qrIBCGY8HeYkliC%2Fd%2Fl0cRlRjFgbO7z5b5fiV5fAUGhHEYcrmgUKhfpIDSQBmig%2F44FNAXBeAyRH0epFU97Nz%2FfzRQKBQKhUKhvrnv%2F0t9WBQKhUKhUKhvLdfd%2FyxlKHX3Pp9B85XCRzClwCwXKzH8ckVmolQZTrmXz4d0qqdjCB2T63Su1jDSGrBajwYKhUKhUEoibmo4jpG6BIwkxhqfMhIQkkevYa0oP2eu8TaAFl9PGZ%2B%2ByT%2Bh8pjuKJbXcG3P%2FXK5r%2B2S2u01FLh%2Bk9nQOIIMSZ8K7XipHqXkzGHb07EPkh5DKaRJ2%2BodN8qhR4agzsdCxjFkgAyxgVKQyTeQC%2BrcDUQ%2BdhjpDVitR0PvIdR63PUmFr0pTG%2By1JyWUSgUCgXW46FKVM8mjptnpeBg9xFfpfyoh8zmdhJ6r0uNFya2jLgVqb%2B3fPppcTuEqEQfC%2BVF4OwBTlBI11CrfjdEbQEsuNpDaoFdy5%2FZevanyvtuTax7acis5wTdps15x6xzZnKjvPICrWtpwPvJabo87xoH%2Bwn76Rv3EwqFQqG%2BSnakmJm4pWSXua%2BRrJWCMRtKAr3HKZ9qEGMyHwaJ3xdGAWKM%2F4xUoInJmDWg0MwwGwKhlRkpGCQyAwWC7itztrm9QmPjOXO2jS5Uz4543U7HuHiBfb%2BoQ7Lffhqk9HKnDbmqew3HFOZIoFAo1N8Q6RvPa3qVrTHbnBcLExyOIEK0xAMWZo9MWFdVHY4UrKyhbh7fgbwOvAmq3HD7ZdfyKt%2F8XdQPdVu%2FDTXsAVBDab1%2BqyltANDQDCFGXg0N1IFineq1OoYIP8vk2841NIYR1MMhno5ftD4ahoo3NT6EcRsS7pJp5RR%2Ff0rZtXS13FFM80WhUChQkEA6x2olMn7dbvj7pW2fXJNQDSpY5kKmBHG%2FUgnybQ2I%2BTjPVs0QzKs0FK1nir%2F9BmjjDitDlk5JNx2o%2FtP99Fsg9xgCT7EChG4sCoVC%2FVjZJLudpsVaL4FNqxz3dOPnxXqaknyyOhIoq53PUTi0k5RUUoEq10WnzLJjKKXUu88Framxvp9aspLGtaxaQn3L%2BR1Zilw93ThIHtxH5pJmyVlzYoPTNqYASbOKM2Ls5vhK%2Byqgu5mC8mBsv%2F9GIw%2F72t4u1J0EEfeO%2Ffhn2dW5%2Bd6q06C5G72V5Z%2FmMpL3vGpApDjbUi0oy7J5aZUDTLTMhGbFoIj04yoeZfLOyyGu2Yp180vIvsYtFMzgmNC80aorm9aJjQCWm4pCoVCor9CDlNO51JJAj05ZhbhAFaA5PdmeUwAcnHU18sLMPZfFBJXjt2FHKtKVLaAsfpC6XZfnzjk7SG0xG%2Fj9aOfqnpHRcDk7Zu4d27Bgdx33wztvWakn16w47NOYt7pa%2FnvUsPnZ6viol5mDPKY3NOPivhrOlJ1DL3k9MpZPcY9np5el7JbHvjzbdh%2Fs%2FLywvfrp%2B012tt6YQtcPZ3Impz4b8IcvOfLuTJ7xjZ9VT%2Bcz7Lbs%2FwLiqQ3VdFvLmb1%2FIoMifmePxdevS6Q9k53IN%2FkwG6xlz066ObvrqcyXj8f6jbdt5iY9PJnJp0P77a1mzm4zF9TJ%2F2Sg5e9YrJtIS33zMY%2Fiw2G0ZGvT6eQSGXvLnsZhcTwLeetrDHdF8%2BnlSPJoXBJ%2Ffo2xfaMgpJj9benkUsx1SVSmlpIP0lDpFWV8jb4trYMGVAdT26sdYZPlYMNC%2BFMI4LTM3vN6M4qVFgAU6iv0D5QnfMaZoP9%2FAAAAAElFTkSuQmCC);
100+
}
101+
102+
.zoom-controls > .zoom-in {
103+
background-position: 0 0;
104+
}
105+
106+
.zoom-controls > .zoom-out {
107+
background-position: 0 -26px;
108+
109+
}
110+
111+
.map-container .label {
112+
position: absolute;
113+
left: 4px;
114+
bottom: 4px;
115+
color: #666;
116+
font-size: 11px;
117+
}
118+
119+
.map-controls {
120+
position: absolute;
121+
top: 10px;
122+
right: 10px;
123+
}
124+
125+
.map-controls a {
126+
display: inline-block;
127+
padding: 2px;
128+
background-color: white;
129+
border-radius: 4px;
130+
border: 2px solid #ddd;
131+
text-decoration: none;
132+
color: darkblue;
133+
font-size: 12px;
134+
}
135+
136+
.zoomer {
137+
position: absolute;
138+
transform-origin: 50% 50%;
139+
}
140+
141+
.tiles-wrapper {
142+
position: absolute;
143+
transform-origin: top left;
144+
top: 0;
145+
left: 0;
146+
}
147+
148+
.tiles-wrapper > img {
149+
position: absolute;
150+
top: 0;
151+
left: 0;
152+
}
153+
154+
.path-group > polyline {
155+
vector-effect: non-scaling-stroke;
156+
fill: none;
157+
stroke-width: 4;
158+
stroke: rgba(255, 0, 0, 0.8);
159+
stroke-linejoin: round;
160+
stroke-linecap: round;
161+
}
162+
163+
a.save-route {
164+
background-image: url(resources/images/save-route.svg);
165+
background-size: 50px 50px;
166+
background-repeat: no-repeat;
167+
text-indent: 20px;
168+
}
169+
170+
a.download-gpx {
171+
background-image: url(resources/images/gpx-file.svg);
172+
background-size: 18px 18px;
173+
background-repeat: no-repeat;
174+
text-indent: 18px;
175+
}
176+
177+
.drop-down {
178+
position: relative;
179+
display: inline-block;
180+
}
181+
182+
a.map-type {
183+
padding-right: 12px;
184+
}
185+
186+
a.map-type::after {
187+
content: '';
188+
display: block;
189+
position: absolute;
190+
top: 10px;
191+
right: 2px;
192+
border-left: 4px solid transparent;
193+
border-right: 4px solid transparent;
194+
border-top: 4px solid black;
195+
}
196+
197+
.photo-placard {
198+
position: absolute;
199+
height: 0px;
200+
width: 0px;
201+
/* This makes it too slow in Safari */
202+
filter: drop-shadow(1px 2px 2px rgba(0, 0, 0, 0.3));
203+
}
204+
205+
.photo-container {
206+
position: absolute;
207+
height: calc(var(--photo-size) + var(--arrow-size));
208+
width: var(--photo-size);
209+
top: calc(-1 * (var(--photo-size) + var(--arrow-size)));
210+
left: calc(var(--photo-size) / -2);
211+
background-color: white;
212+
box-sizing: border-box;
213+
padding: 4px;
214+
--arrow-leadin: calc((var(--photo-size) - 2 * var(--arrow-size)) / 2);
215+
clip-path: polygon(
216+
0 0,
217+
var(--photo-size) 0,
218+
var(--photo-size) var(--photo-size),
219+
calc(var(--photo-size) - var(--arrow-leadin)) var(--photo-size),
220+
calc(var(--photo-size) / 2) calc(var(--photo-size) + var(--arrow-size)),
221+
var(--arrow-leadin) var(--photo-size),
222+
0 var(--photo-size));
223+
}
224+
225+
.photo-container > img {
226+
width: 100%;
227+
aspect-ratio: 1;
228+
object-fit: cover;
229+
}
230+
231+
</style>
232+
</head>
233+
<body>
234+
<div id="stage">
235+
<div id="container"></div>
236+
</div>
237+
<script src="../../../resources/strings.js"></script>
238+
<script src="../../../resources/extensions.js"></script>
239+
<script src="../../../resources/statistics.js"></script>
240+
<script src="../../resources/math.js"></script>
241+
<script src="../../resources/benchmark.js"></script>
242+
<script src="../../resources/controllers.js"></script>
243+
<script src="../../resources/stage.js"></script>
244+
<script src="resources/map-zoomer.js"></script>
245+
</body>
246+
</html>
Lines changed: 2 additions & 0 deletions
Loading
794 KB
Loading
883 KB
Loading
639 KB
Loading
816 KB
Loading
841 KB
Loading

0 commit comments

Comments
 (0)