Initial commit
This commit is contained in:
commit
dc29454df7
18 changed files with 2275 additions and 0 deletions
3
src/app.css
Normal file
3
src/app.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
12
src/app.html
Normal file
12
src/app.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
1
src/lib/index.js
Normal file
1
src/lib/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
||||
7
src/routes/+page.svelte
Normal file
7
src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import Map from './leaflet.svelte';
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Map />
|
||||
</main>
|
||||
1
src/routes/+page.ts
Normal file
1
src/routes/+page.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const ssr =false;
|
||||
310
src/routes/leaflet.svelte
Normal file
310
src/routes/leaflet.svelte
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import * as L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
/**
|
||||
* @type {{ removeLayer: (arg0: any) => void; setView: (arg0: number[], arg1: any) => void; getZoom: () => any; on: (arg0: string, arg1: (e: any) => void) => void; }}
|
||||
*/
|
||||
let map;
|
||||
let mouseLat = 0;
|
||||
let mouseLng = 0;
|
||||
/**
|
||||
* @type {null}
|
||||
*/
|
||||
let marker = null;
|
||||
|
||||
let startPoint = 'Custom';
|
||||
let startHeight = 0;
|
||||
let startTime = '13:13';
|
||||
let startDate = new Date(2025, 2, 24);
|
||||
let ascentRate = 5;
|
||||
let burstAltitude = 30000;
|
||||
let flightProfile = 'Normal';
|
||||
let descentRate = 5;
|
||||
let forecastMode = 'Single';
|
||||
let inputLat = '56.3576';
|
||||
let inputLng = '39.8666';
|
||||
|
||||
const updateMapPosition = () => {
|
||||
const lat = parseFloat(inputLat);
|
||||
const lng = parseFloat(inputLng);
|
||||
|
||||
if (isNaN(lat)) {
|
||||
alert("Please enter a valid latitude");
|
||||
return;
|
||||
}
|
||||
if (isNaN(lng)) {
|
||||
alert("Please enter a valid longitude");
|
||||
return;
|
||||
}
|
||||
if (lat < -90 || lat > 90) {
|
||||
alert("Latitude must be between -90 and 90");
|
||||
return;
|
||||
}
|
||||
if (lng < -180 || lng > 180) {
|
||||
alert("Longitude must be between -180 and 180");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove existing marker if it exists
|
||||
if (marker) {
|
||||
map.removeLayer(marker);
|
||||
}
|
||||
|
||||
// Create new marker
|
||||
marker = L.marker([lat, lng]).addTo(map)
|
||||
.bindPopup("Launch Point");
|
||||
|
||||
// Center map on new coordinates
|
||||
map.setView([lat, lng], map.getZoom());
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
map = L.map('map').setView([51.505, -0.09], 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map);
|
||||
|
||||
map.on('mousemove', (e) => {
|
||||
mouseLat = e.latlng.lat.toFixed(6);
|
||||
mouseLng = e.latlng.lng.toFixed(6);
|
||||
});
|
||||
|
||||
marker = L.marker([56.3576, 39.8666]).addTo(map)
|
||||
.bindPopup(() => {
|
||||
return `
|
||||
<b>Launch Point</b><br>, Lat: ${marker.getLatLng().lat.toFixed(6)}<br>, Long: ${marker.getLatLng().lng.toFixed(6)}<br>
|
||||
`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="map-container">
|
||||
<div id="map"></div>
|
||||
<div class="coordinates-display">
|
||||
Lat: {mouseLat}, Long: {mouseLng}
|
||||
</div>
|
||||
<div class="control-panel">
|
||||
<div class="control-header">Launch Point</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Start Point:</span>
|
||||
<select bind:value={startPoint}>
|
||||
<option>Custom</option>
|
||||
<option>Preset 1</option>
|
||||
<option>Preset 2</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Latitude/Longitude:</span>
|
||||
<div class="coordinate-inputs">
|
||||
<input type="text" bind:value={inputLat} placeholder="Latitude">
|
||||
<p>/</p>
|
||||
<input type="text" bind:value={inputLng} placeholder="Longitude">
|
||||
<button on:click={updateMapPosition} class="update-button">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<button class="map-button">Specify on map</button>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Launch Height (m):</span>
|
||||
<input type="number" bind:value={startHeight}>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Launch Time (UTC):</span>
|
||||
<input type="time" bind:value={startTime}>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Launch Date:</span>
|
||||
<input type="date" bind:value={startDate}>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Ascent Rate (m/s):</span>
|
||||
<input type="number" bind:value={ascentRate}>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Burst/Drift Altitude (m):</span>
|
||||
<input type="number" bind:value={burstAltitude}>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Flight Profile:</span>
|
||||
<select bind:value={flightProfile}>
|
||||
<option>Normal</option>
|
||||
<option>Custom</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-row buttons">
|
||||
<button>Open Burst Calculator</button>
|
||||
<button>Set Custom Flight Profile</button>
|
||||
<button>Show Last Altitude Graph</button>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Descent Rate (m/s):</span>
|
||||
<input type="number" bind:value={descentRate}>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<span>Forecast Mode (help):</span>
|
||||
<div class="radio-group">
|
||||
<label>
|
||||
<input type="radio" bind:group={forecastMode} value="Single"> Single
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={forecastMode} value="Multiple"> Multiple
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<button class="primary-button">Get Forecast</button>
|
||||
<button>Save Point</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.map-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.coordinates-display {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
z-index: 1000; /* Ensure it's above the map */
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.control-panel {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
width: 320px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.control-header {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.control-row span {
|
||||
width: 160px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.control-row input[type="number"],
|
||||
.control-row input[type="date"],
|
||||
.control-row input[type="time"],
|
||||
.control-row select {
|
||||
width: 120px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.coordinate-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.coordinate-inputs input {
|
||||
width: 70px !important;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.update-button {
|
||||
padding: 3px 8px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.map-button {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.buttons button {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
margin-bottom: 5px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: #4CAF50 !important;
|
||||
color: white;
|
||||
border: 1px solid #3e8e41 !important;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.radio-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue