3dmapgen / index.html
W8in4am8's picture
Upload index.html with huggingface_hub
8b16df9 verified
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Map Generator</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
:root {
--primary-color: #4361ee;
--secondary-color: #3f37c9;
--accent-color: #4895ef;
--text-color: #f8f9fa;
--bg-color: #121212;
--panel-bg: #1e1e1e;
--border-color: #333333;
--hover-color: #2a2a2a;
--success-color: #4cc9f0;
--warning-color: #f72585;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
height: 100vh;
overflow: hidden;
}
.app-container {
display: grid;
grid-template-columns: 1fr 350px;
grid-template-rows: 60px 1fr 200px;
grid-template-areas:
"header header"
"map viewer"
"controls controls";
height: 100vh;
gap: 10px;
padding: 10px;
}
header {
grid-area: header;
background-color: var(--panel-bg);
border-radius: 8px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
}
.logo {
font-size: 1.8rem;
font-weight: 700;
background: linear-gradient(90deg, var(--accent-color), var(--success-color));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.search-container {
display: flex;
gap: 10px;
width: 50%;
}
#search-input {
flex: 1;
padding: 12px 15px;
border-radius: 6px;
border: 1px solid var(--border-color);
background-color: #2d2d2d;
color: var(--text-color);
font-size: 1rem;
}
#search-btn {
padding: 12px 20px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: background-color 0.3s;
}
#search-btn:hover {
background-color: var(--secondary-color);
}
.map-container {
grid-area: map;
position: relative;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
}
#map {
height: 100%;
width: 100%;
}
.viewer-container {
grid-area: viewer;
background-color: var(--panel-bg);
border-radius: 8px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
}
.viewer-header {
padding: 15px;
background-color: var(--hover-color);
font-weight: 600;
border-bottom: 1px solid var(--border-color);
}
#model-viewer {
flex: 1;
background-color: #000;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 1.2rem;
}
.controls-container {
grid-area: controls;
background-color: var(--panel-bg);
border-radius: 8px;
padding: 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
overflow-y: auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
}
.control-group {
background-color: var(--hover-color);
border-radius: 6px;
padding: 15px;
border: 1px solid var(--border-color);
}
.control-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.control-title {
font-size: 1.1rem;
font-weight: 600;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #555;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--primary-color);
}
input:checked + .slider:before {
transform: translateX(26px);
}
.slider-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.control-item {
display: flex;
flex-direction: column;
gap: 8px;
}
label {
font-size: 0.9rem;
opacity: 0.8;
}
select, input[type="color"], input[type="range"] {
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: #2d2d2d;
color: var(--text-color);
}
input[type="range"] {
padding: 0;
}
.range-value {
text-align: center;
font-size: 0.9rem;
opacity: 0.8;
}
.export-container {
display: flex;
gap: 15px;
margin-top: 20px;
}
.export-btn {
flex: 1;
padding: 12px;
background-color: var(--success-color);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
}
.export-btn:hover {
background-color: #3aaed8;
transform: translateY(-2px);
}
.export-btn:active {
transform: translateY(0);
}
.bbox-controls {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background-color: var(--panel-bg);
border-radius: 6px;
padding: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
}
.bbox-btn {
padding: 8px 12px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
font-size: 0.9rem;
}
.bbox-btn:hover {
background-color: var(--secondary-color);
}
#draw-bbox {
background-color: var(--warning-color);
}
#draw-bbox:hover {
background-color: #d3166a;
}
.instructions {
position: absolute;
bottom: 10px;
left: 10px;
z-index: 1000;
background-color: rgba(30, 30, 30, 0.8);
border-radius: 6px;
padding: 10px;
font-size: 0.8rem;
max-width: 300px;
}
@media (max-width: 1024px) {
.app-container {
grid-template-columns: 1fr;
grid-template-rows: 60px 1fr 1fr 250px;
grid-template-areas:
"header"
"map"
"viewer"
"controls";
}
}
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: flex-start;
padding: 10px;
}
.search-container {
width: 100%;
margin-top: 10px;
}
.controls-container {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="app-container">
<header>
<div class="logo">3D Map Generator</div>
<div class="search-container">
<input type="text" id="search-input" placeholder="Search for a location (e.g. Sydney, Australia)">
<button id="search-btn">Search</button>
</div>
</header>
<div class="map-container">
<div id="map"></div>
<div class="bbox-controls">
<button id="draw-bbox" class="bbox-btn">Draw Bounding Box</button>
<button id="clear-bbox" class="bbox-btn">Clear Box</button>
</div>
<div class="instructions">
<p>1. Search for a location or pan/zoom to your area of interest</p>
<p>2. Click "Draw Bounding Box" and drag on the map to select an area</p>
<p>3. Adjust the box by dragging its corners or edges</p>
<p>4. Configure your 3D model settings in the control panel</p>
<p>5. Click OBJ or GLB export to download your model</p>
</div>
</div>
<div class="viewer-container">
<div class="viewer-header">3D Model Viewer</div>
<div id="model-viewer">3D Model will appear here after generation</div>
</div>
<div class="controls-container">
<div class="control-group">
<div class="control-header">
<div class="control-title">Imagery Source</div>
</div>
<div class="slider-content">
<div class="control-item">
<label for="imagery-source">Source</label>
<select id="imagery-source">
<option value="maptiler">MapTiler</option>
<option value="mapbox">Mapbox</option>
<option value="google">Google Earth (API Required)</option>
</select>
</div>
<div class="control-item">
<label for="imagery-type">Type</label>
<select id="imagery-type">
<option value="clean">Clean (No Labels)</option>
<option value="labeled">With Labels</option>
</select>
</div>
<div class="control-item">
<label for="maptiler-key">MapTiler API Key</label>
<input type="text" id="maptiler-key" value="K05sQSujRJ1QvWTl5Nt1">
</div>
<div class="control-item">
<label for="mapbox-key">Mapbox API Key</label>
<input type="text" id="mapbox-key" value="pk.eyJ1IjoidzhpbjRhbTgiLCJhIjoiY21jdDczMm82MDBmYzJrcjN0a3hoaHZuNSJ9.NJXk-4YN0odvrf3yA5VPjQ">
</div>
</div>
</div>
<div class="control-group">
<div class="control-header">
<div class="control-title">Elevation Settings</div>
<label class="toggle-switch">
<input type="checkbox" id="elevation-toggle" checked>
<span class="slider"></span>
</label>
</div>
<div class="slider-content">
<div class="control-item">
<label for="elevation-exaggeration">Exaggeration</label>
<input type="range" id="elevation-exaggeration" min="1" max="5" step="0.1" value="1.5">
</div>
<div class="control-item">
<div class="range-value" id="elevation-value">1.5x</div>
</div>
</div>
</div>
<div class="control-group">
<div class="control-header">
<div class="control-title">Buildings</div>
<label class="toggle-switch">
<input type="checkbox" id="buildings-toggle">
<span class="slider"></span>
</label>
</div>
<div class="slider-content">
<div class="control-item">
<label for="buildings-height">Height Exaggeration</label>
<input type="range" id="buildings-height" min="1" max="3" step="0.1" value="1">
</div>
<div class="control-item">
<div class="range-value" id="buildings-height-value">1.0x</div>
</div>
<div class="control-item">
<label for="buildings-color">Color</label>
<input type="color" id="buildings-color" value="#4361ee">
</div>
<div class="control-item">
<label>&nbsp;</label>
<button class="export-btn" id="generate-btn">Generate 3D Mesh</button>
</div>
</div>
</div>
<div class="control-group">
<div class="control-header">
<div class="control-title">Trees</div>
<label class="toggle-switch">
<input type="checkbox" id="trees-toggle">
<span class="slider"></span>
</label>
</div>
<div class="slider-content">
<div class="control-item">
<label for="trees-density">Density (Bushland)</label>
<input type="range" id="trees-density" min="1" max="10" step="1" value="5">
</div>
<div class="control-item">
<div class="range-value" id="trees-density-value">1 tree/100m</div>
</div>
</div>
</div>
<div class="control-group">
<div class="control-header">
<div class="control-title">Roads</div>
<label class="toggle-switch">
<input type="checkbox" id="roads-toggle">
<span class="slider"></span>
</label>
</div>
<div class="slider-content">
<div class="control-item">
<label for="roads-height">Height Exaggeration</label>
<input type="range" id="roads-height" min="1" max="3" step="0.1" value="1">
</div>
<div class="control-item">
<div class="range-value" id="roads-height-value">1.0x</div>
</div>
<div class="control-item">
<label for="roads-width">Width Exaggeration</label>
<input type="range" id="roads-width" min="1" max="3" step="0.1" value="1">
</div>
<div class="control-item">
<div class="range-value" id="roads-width-value">1.0x</div>
</div>
<div class="control-item">
<label for="roads-color">Color</label>
<input type="color" id="roads-color" value="#f72585">
</div>
</div>
</div>
<div class="control-group">
<div class="control-header">
<div class="control-title">Pathways</div>
<label class="toggle-switch">
<input type="checkbox" id="paths-toggle">
<span class="slider"></span>
</label>
</div>
<div class="slider-content">
<div class="control-item">
<label for="paths-height">Height Exaggeration</label>
<input type="range" id="paths-height" min="1" max="3" step="0.1" value="1">
</div>
<div class="control-item">
<div class="range-value" id="paths-height-value">1.0x</div>
</div>
<div class="control-item">
<label for="paths-width">Width Exaggeration</label>
<input type="range" id="paths-width" min="1" max="3" step="0.1" value="1">
</div>
<div class="control-item">
<div class="range-value" id="paths-width-value">1.0x</div>
</div>
<div class="control-item">
<label for="paths-color">Color</label>
<input type="color" id="paths-color" value="#4cc9f0">
</div>
</div>
</div>
<div class="export-container">
<button class="export-btn" id="export-obj">Export as OBJ</button>
<button class="export-btn" id="export-glb">Export as GLB</button>
</div>
</div>
</div>
<script>
// Initialize the map
const map = L.map('map').setView([0, 0], 2);
// Add base tile layer (MapTiler satellite as default)
const baseLayer = L.tileLayer('https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=K05sQSujRJ1QvWTl5Nt1', {
attribution: '&copy; MapTiler',
maxZoom: 18
}).addTo(map);
// Bounding box variables
let boundingBox = null;
let isDrawing = false;
// Initialize bounding box drawing
document.getElementById('draw-bbox').addEventListener('click', () => {
if (boundingBox) {
map.removeLayer(boundingBox);
}
// Create a temporary rectangle for drawing
const southWest = map.getBounds().getSouthWest();
const northEast = map.getBounds().getNorthEast();
const bounds = [[southWest.lat, southWest.lng], [northEast.lat, northEast.lng]];
boundingBox = L.rectangle(bounds, {
color: "#ff7800",
weight: 2,
fillOpacity: 0.1
}).addTo(map);
// Enable editing
boundingBox.editing.enable();
});
// Clear bounding box
document.getElementById('clear-bbox').addEventListener('click', () => {
if (boundingBox) {
map.removeLayer(boundingBox);
boundingBox = null;
}
});
// Search functionality
document.getElementById('search-btn').addEventListener('click', () => {
const query = document.getElementById('search-input').value;
if (query) {
fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
if (data && data[0]) {
const lat = parseFloat(data[0].lat);
const lon = parseFloat(data[0].lon);
map.setView([lat, lon], 15);
}
})
.catch(error => console.error('Search error:', error));
}
});
// Handle Enter key in search input
document.getElementById('search-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
document.getElementById('search-btn').click();
}
});
// Elevation exaggeration slider
const elevationSlider = document.getElementById('elevation-exaggeration');
const elevationValue = document.getElementById('elevation-value');
elevationSlider.addEventListener('input', () => {
elevationValue.textContent = elevationSlider.value + 'x';
});
// Buildings height slider
const buildingsHeightSlider = document.getElementById('buildings-height');
const buildingsHeightValue = document.getElementById('buildings-height-value');
buildingsHeightSlider.addEventListener('input', () => {
buildingsHeightValue.textContent = buildingsHeightSlider.value + 'x';
});
// Trees density slider
const treesDensitySlider = document.getElementById('trees-density');
const treesDensityValue = document.getElementById('trees-density-value');
treesDensitySlider.addEventListener('input', () => {
treesDensityValue.textContent = `1 tree/${100/treesDensitySlider.value}m`;
});
// Roads height slider
const roadsHeightSlider = document.getElementById('roads-height');
const roadsHeightValue = document.getElementById('roads-height-value');
roadsHeightSlider.addEventListener('input', () => {
roadsHeightValue.textContent = roadsHeightSlider.value + 'x';
});
// Roads width slider
const roadsWidthSlider = document.getElementById('roads-width');
const roadsWidthValue = document.getElementById('roads-width-value');
roadsWidthSlider.addEventListener('input', () => {
roadsWidthValue.textContent = roadsWidthSlider.value + 'x';
});
// Paths height slider
const pathsHeightSlider = document.getElementById('paths-height');
const pathsHeightValue = document.getElementById('paths-height-value');
pathsHeightSlider.addEventListener('input', () => {
pathsHeightValue.textContent = pathsHeightSlider.value + 'x';
});
// Paths width slider
const pathsWidthSlider = document.getElementById('paths-width');
const pathsWidthValue = document.getElementById('paths-width-value');
pathsWidthSlider.addEventListener('input', () => {
pathsWidthValue.textContent = pathsWidthSlider.value + 'x';
});
// Imagery source change handler
document.getElementById('imagery-source').addEventListener('change', (e) => {
const source = e.target.value;
const key = source === 'maptiler' ?
document.getElementById('maptiler-key').value :
document.getElementById('mapbox-key').value;
map.eachLayer(layer => {
if (layer instanceof L.TileLayer) {
map.removeLayer(layer);
}
});
let url;
if (source === 'maptiler') {
url = `https://api.maptiler.com/maps/${document.getElementById('imagery-type').value === 'clean' ? 'satellite' : 'hybrid'}/{z}/{x}/{y}.jpg?key=${key}`;
} else if (source === 'mapbox') {
url = `https://api.mapbox.com/styles/v1/mapbox/${document.getElementById('imagery-type').value === 'clean' ? 'satellite-v9' : 'satellite-streets-v12'}/tiles/{z}/{x}/{y}?access_token=${key}`;
} else {
// Google Earth placeholder
url = 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}';
}
L.tileLayer(url, {
attribution: `&copy; ${source}`,
maxZoom: 18
}).addTo(map);
});
// Imagery type change handler
document.getElementById('imagery-type').addEventListener('change', () => {
document.getElementById('imagery-source').dispatchEvent(new Event('change'));
});
// Generate 3D mesh button
document.getElementById('generate-btn').addEventListener('click', () => {
const viewer = document.getElementById('model-viewer');
viewer.innerHTML = `
<div style="text-align:center">
<div style="font-size:3rem;margin-bottom:10px">🌍</div>
<div>Generating 3D mesh...</div>
<div style="margin-top:10px;font-size:0.9rem;opacity:0.8">This would normally download elevation data and satellite imagery</div>
</div>
`;
// Simulate processing time
setTimeout(() => {
viewer.innerHTML = `
<div style="text-align:center">
<div style="font-size:3rem;margin-bottom:10px">✅</div>
<div>3D mesh generated successfully!</div>
<div style="margin-top:10px;font-size:0.9rem;opacity:0.8">Adjust settings and click export to download</div>
</div>
`;
}, 1500);
});
// Export buttons
document.getElementById('export-obj').addEventListener('click', () => {
exportModel('OBJ');
});
document.getElementById('export-glb').addEventListener('click', () => {
exportModel('GLB');
});
function exportModel(format) {
if (!boundingBox) {
alert('Please draw a bounding box first');
return;
}
const viewer = document.getElementById('model-viewer');
viewer.innerHTML = `
<div style="text-align:center">
<div style="font-size:3rem;margin-bottom:10px">📦</div>
<div>Exporting as ${format}...</div>
<div style="margin-top:10px;font-size:0.9rem;opacity:0.8">Preparing download</div>
</div>
`;
// Simulate export process
setTimeout(() => {
// Create a download link
const blob = new Blob([`3D Model data in ${format} format`], {type: 'application/octet-stream'});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `map-3d-model.${format.toLowerCase()}`;
document.body.appendChild(a);
a.click();
// Clean up
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 100);
viewer.innerHTML = `
<div style="text-align:center">
<div style="font-size:3rem;margin-bottom:10px">💾</div>
<div>${format} file downloaded!</div>
<div style="margin-top:10px;font-size:0.9rem;opacity:0.8">Your 3D model is ready for use</div>
</div>
`;
}, 2000);
}
// Toggle event listeners
document.getElementById('elevation-toggle').addEventListener('change', function() {
if (this.checked) {
document.getElementById('elevation-exaggeration').parentElement.parentElement.style.opacity = '1';
document.getElementById('elevation-exaggeration').disabled = false;
} else {
document.getElementById('elevation-exaggeration').parentElement.parentElement.style.opacity = '0.5';
document.getElementById('elevation-exaggeration').disabled = true;
}
});
document.getElementById('buildings-toggle').addEventListener('change', function() {
if (this.checked) {
document.getElementById('buildings-height').parentElement.parentElement.style.opacity = '1';
document.getElementById('buildings-color').parentElement.parentElement.style.opacity = '1';
document.getElementById('buildings-height').disabled = false;
document.getElementById('buildings-color').disabled = false;
} else {
document.getElementById('buildings-height').parentElement.parentElement.style.opacity = '0.5';
document.getElementById('buildings-color').parentElement.parentElement.style.opacity = '0.5';
document.getElementById('buildings-height').disabled = true;
document.getElementById('buildings-color').disabled = true;
}
});
document.getElementById('trees-toggle').addEventListener('change', function() {
if (this.checked) {
document.getElementById('trees-density').parentElement.parentElement.style.opacity = '1';
document.getElementById('trees-density').disabled = false;
} else {
document.getElementById('trees-density').parentElement.parentElement.style.opacity = '0.5';
document.getElementById('trees-density').disabled = true;
}
});
document.getElementById('roads-toggle').addEventListener('change', function() {
if (this.checked) {
document.querySelectorAll('#roads-height, #roads-width, #roads-color').forEach(el => {
el.parentElement.parentElement.style.opacity = '1';
el.disabled = false;
});
} else {
document.querySelectorAll('#roads-height, #roads-width, #roads-color').forEach(el => {
el.parentElement.parentElement.style.opacity = '0.5';
el.disabled = true;
});
}
});
document.getElementById('paths-toggle').addEventListener('change', function() {
if (this.checked) {
document.querySelectorAll('#paths-height, #paths-width, #paths-color').forEach(el => {
el.parentElement.parentElement.style.opacity = '1';
el.disabled = false;
});
} else {
document.querySelectorAll('#paths-height, #paths-width, #paths-color').forEach(el => {
el.parentElement.parentElement.style.opacity = '0.5';
el.disabled = true;
});
}
});
</script>
</body>
</html>