mirror of
https://github.com/garraflavatra/gpstool.git
synced 2025-01-18 03:37:58 +00:00
Initial commit
This commit is contained in:
commit
5a39fec5ad
BIN
.github/screenshot.png
vendored
Normal file
BIN
.github/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 726 KiB |
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
19
LICENSE.md
Normal file
19
LICENSE.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2023 Romein van Buren <<romein@vburen.nl>>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
18
README.md
Normal file
18
README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# gpstool
|
||||||
|
|
||||||
|
A web-based tool for retrieving and converting GPS coordinates.
|
||||||
|
|
||||||
|
![Screenshot](./.github/screenshot.png)
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
gpstool is developed using [Vite](https://vitejs.dev/) and [Svelte](https://svelte.dev/).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Please see the [`LICENSE.md` file](./LICENSE.md) for the license.
|
18
index.html
Normal file
18
index.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>gpstool</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<noscript>
|
||||||
|
Please enable JavaScript in your browser to use gpstool.
|
||||||
|
</noscript>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="/src/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
32
jsconfig.json
Normal file
32
jsconfig.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
/**
|
||||||
|
* svelte-preprocess cannot figure out whether you have
|
||||||
|
* a value or a type, so tell TypeScript to enforce using
|
||||||
|
* `import type` instead of `import` for Types.
|
||||||
|
*/
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
/**
|
||||||
|
* To have warnings / errors of the Svelte compiler at the
|
||||||
|
* correct position, enable source maps by default.
|
||||||
|
*/
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
/**
|
||||||
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
|
* Disable this if you'd like to use dynamic types.
|
||||||
|
*/
|
||||||
|
"checkJs": true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Use global.d.ts instead of compilerOptions.types
|
||||||
|
* to avoid limiting type declarations.
|
||||||
|
*/
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||||
|
}
|
5565
package-lock.json
generated
Normal file
5565
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
package.json
Normal file
26
package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "gpstool",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@garraflavatra/yeslint": "^1.0.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^2.4.2",
|
||||||
|
"eslint": "^8.51.0",
|
||||||
|
"eslint-plugin-svelte": "^2.34.0",
|
||||||
|
"svelte": "^4.0.5",
|
||||||
|
"vite": "^4.4.5"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "./node_modules/@garraflavatra/yeslint/configs/svelte.js",
|
||||||
|
"rules": {
|
||||||
|
"array-bracket-newline": "off",
|
||||||
|
"array-element-newline": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
src/app.css
Normal file
79
src/app.css
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
html, body {
|
||||||
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 32px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
ul li, ol li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
dl dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
dl dd {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.card h2 {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font: inherit;
|
||||||
|
border: 1px solid #888;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
*:last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
html, body {
|
||||||
|
background-color: #333;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { color: #ade; }
|
||||||
|
|
||||||
|
.card { background-color: #444; }
|
||||||
|
.card h2 { border-color: #333; }
|
||||||
|
}
|
8
src/app.js
Normal file
8
src/app.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import './app.css';
|
||||||
|
import App from './app.svelte';
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.getElementById('app'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
116
src/app.svelte
Normal file
116
src/app.svelte
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<script>
|
||||||
|
/* global L */
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { latLngToDMS, latLngToDecimal, latLngToExif } from './conversion.js';
|
||||||
|
import { getAppleUrl, getDuckDuckGoUrl, getGoogleUrl } from './urls.js';
|
||||||
|
|
||||||
|
let map;
|
||||||
|
let mapDiv;
|
||||||
|
let marker;
|
||||||
|
let coords = L.latLng(51.507, -0.1275);
|
||||||
|
|
||||||
|
function updateMap() {
|
||||||
|
map.setView(coords);
|
||||||
|
marker.setLatLng(coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
map = L.map(mapDiv).setView(coords, 9);
|
||||||
|
|
||||||
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
L.polyline([ [ -90, 0 ], [ 90, 0 ] ]).addTo(map);
|
||||||
|
L.polyline([ [ 0, -180 ], [ 0, 180 ] ]).addTo(map);
|
||||||
|
|
||||||
|
marker = L.marker(coords).addTo(map);
|
||||||
|
|
||||||
|
map.on('click', event => {
|
||||||
|
coords = event.latlng;
|
||||||
|
updateMap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="grid">
|
||||||
|
<div class="info">
|
||||||
|
<section class="card">
|
||||||
|
<h2>Coordinates</h2>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>Decimal</dt>
|
||||||
|
<dd>{latLngToDecimal(coords)}</dd>
|
||||||
|
|
||||||
|
<dt>DMS</dt>
|
||||||
|
<dd>{latLngToDMS(coords)}</dd>
|
||||||
|
|
||||||
|
<dt>EXIF</dt>
|
||||||
|
<dd>{latLngToExif(coords)}</dd>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>Go to</h2>
|
||||||
|
<div class="coordinate-input">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
step={0.25}
|
||||||
|
bind:value={coords.lat}
|
||||||
|
on:change={updateMap}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
step={0.25}
|
||||||
|
bind:value={coords.lng}
|
||||||
|
on:change={updateMap}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>Open in</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href={getAppleUrl(coords)} target="_blank">Apple Maps</a></li>
|
||||||
|
<li><a href={getGoogleUrl(coords)} target="_blank">Google Maps</a></li>
|
||||||
|
<li><a href={getDuckDuckGoUrl(coords)} target="_blank">DuckDuckGo</a></li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Star gpstool on
|
||||||
|
<a href="https://github.com/garraflavatra/gpstool" target="_blank" rel="noopener noreferrer">
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="map" bind:this={mapDiv} />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template: 1fr / 250px 1fr;
|
||||||
|
height: 100%;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
@media (max-width: 750px) {
|
||||||
|
.grid {
|
||||||
|
grid-template: 1fr 2fr / 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinate-input {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-template: 1fr / 1fr 1fr;
|
||||||
|
}
|
||||||
|
</style>
|
37
src/conversion.js
Normal file
37
src/conversion.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export function latLngToDecimal({ lat, lng }) {
|
||||||
|
const precision = 5;
|
||||||
|
return `${lat.toFixed(precision)}; ${lng.toFixed(precision)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DMS = degrees, minutes, seconds
|
||||||
|
function decimalToDMS(coord) {
|
||||||
|
const negative = coord < 0;
|
||||||
|
coord = Math.abs(coord);
|
||||||
|
|
||||||
|
const degrees = Math.trunc(coord);
|
||||||
|
let minutes = (coord - degrees) * 60;
|
||||||
|
const seconds = Math.trunc((minutes - Math.trunc(minutes)) * 60);
|
||||||
|
minutes = Math.trunc(minutes);
|
||||||
|
|
||||||
|
return { degrees, minutes, seconds, negative };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function latLngToDMS({ lat, lng }) {
|
||||||
|
lat = decimalToDMS(lat);
|
||||||
|
lng = decimalToDMS(lng);
|
||||||
|
const latRef = lat.negative ? 'S' : 'N';
|
||||||
|
const lngRef = lng.negative ? 'W' : 'E';
|
||||||
|
|
||||||
|
return `${lat.degrees}°${lat.minutes}'${lat.seconds}"${latRef} ` +
|
||||||
|
`${lng.degrees}°${lng.minutes}'${lng.seconds}"${lngRef}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function latLngToExif({ lat, lng }) {
|
||||||
|
lat = decimalToDMS(lat);
|
||||||
|
lng = decimalToDMS(lng);
|
||||||
|
const latRef = lat.negative ? 'S' : 'N';
|
||||||
|
const lngRef = lng.negative ? 'W' : 'E';
|
||||||
|
|
||||||
|
return `${lat.degrees},${lat.minutes}.${lat.seconds}${latRef} ` +
|
||||||
|
`${lng.degrees},${lng.minutes}.${lng.seconds}${lngRef}`;
|
||||||
|
}
|
17
src/urls.js
Normal file
17
src/urls.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { latLngToDMS } from './conversion.js';
|
||||||
|
|
||||||
|
export function getAppleUrl({ lat, lng }) {
|
||||||
|
return `https://maps.apple.com/?address=${lat},${lng}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGoogleUrl(latlng) {
|
||||||
|
return `https://www.google.com/maps/place/${encodeURIComponent(
|
||||||
|
latLngToDMS(latlng)
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDuckDuckGoUrl(latlng) {
|
||||||
|
return `https://duckduckgo.com/?q=${encodeURIComponent(
|
||||||
|
latLngToDMS(latlng)
|
||||||
|
)}&iaxm=maps`;
|
||||||
|
}
|
2
src/vite-env.d.ts
vendored
Normal file
2
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
8
svelte.config.js
Normal file
8
svelte.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
compilerOptions: {
|
||||||
|
cssHash: ({ css, hash }) => `_${hash(css)}`,
|
||||||
|
},
|
||||||
|
};
|
6
vite.config.js
Normal file
6
vite.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [ svelte() ],
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user