Created
November 19, 2025 10:05
-
-
Save dsebastien/68f7032eb646bd90e84b50e0769f3489 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| // Configuration | |
| const OUTPUT_DIR = '...'; // Relative path to the folder where landmark notes should be created | |
| const FILE_PREFIX = 'Foo'; // Prefix for the landmark notes filenames | |
| /** | |
| * Get all KML files in the current directory | |
| */ | |
| function getKMLFiles() { | |
| const files = fs.readdirSync('.'); | |
| return files.filter(file => file.toLowerCase().endsWith('.kml')); | |
| } | |
| /** | |
| * Get the next available ID by checking existing files (not mandatory, but I just decided to give each landmark note a unique identifier) | |
| */ | |
| function getNextId() { | |
| if (!fs.existsSync(OUTPUT_DIR)) { | |
| fs.mkdirSync(OUTPUT_DIR, { recursive: true }); | |
| return 1; | |
| } | |
| const files = fs.readdirSync(OUTPUT_DIR); | |
| const noteFiles = files.filter(file => file.startsWith(FILE_PREFIX) && file.endsWith('.md')); | |
| if (noteFiles.length === 0) { | |
| return 1; | |
| } | |
| // Extract IDs from filenames | |
| const ids = noteFiles.map(file => { | |
| const match = file.match(/(\d+)\.md$/); | |
| return match ? parseInt(match[1], 10) : 0; | |
| }); | |
| return Math.max(...ids) + 1; | |
| } | |
| /** | |
| * Parse KML file and extract placemarks with PlacemarkStyle | |
| */ | |
| function parsePlacemarks(kmlFilePath) { | |
| const content = fs.readFileSync(kmlFilePath, 'utf8'); | |
| const placemarks = []; | |
| // Split content into individual placemark sections | |
| const placemarkRegex = /<Placemark[^>]*>[\s\S]*?<\/Placemark>/gi; | |
| const placemarkMatches = content.match(placemarkRegex) || []; | |
| for (const placemarkText of placemarkMatches) { | |
| // Check if it uses PlacemarkStyle | |
| const styleUrlMatch = placemarkText.match(/<styleUrl>\s*#([^<]+)\s*<\/styleUrl>/i); | |
| if (!styleUrlMatch || styleUrlMatch[1].trim() !== 'PlacemarkStyle') { | |
| continue; | |
| } | |
| // Extract name | |
| const nameMatch = placemarkText.match(/<name>\s*([^<]+)\s*<\/name>/i); | |
| const name = nameMatch ? nameMatch[1].trim() : 'Cam'; | |
| // Extract coordinates | |
| const coordinatesMatch = placemarkText.match(/<coordinates>\s*([^<]+)\s*<\/coordinates>/i); | |
| if (!coordinatesMatch) { | |
| continue; | |
| } | |
| const coordsText = coordinatesMatch[1].trim(); | |
| const [longitude, latitude, altitude] = coordsText.split(',').map(s => s.trim()); | |
| placemarks.push({ | |
| name, | |
| latitude, | |
| longitude, | |
| altitude | |
| }); | |
| } | |
| return placemarks; | |
| } | |
| /** | |
| * Format number with leading zeros | |
| */ | |
| function padId(id) { | |
| return id.toString().padStart(4, '0'); | |
| } | |
| /** | |
| * Get current timestamp in ISO format | |
| */ | |
| function getTimestamp() { | |
| const now = new Date(); | |
| const year = now.getFullYear(); | |
| const month = String(now.getMonth() + 1).padStart(2, '0'); | |
| const day = String(now.getDate()).padStart(2, '0'); | |
| const hours = String(now.getHours()).padStart(2, '0'); | |
| const minutes = String(now.getMinutes()).padStart(2, '0'); | |
| return `${year}-${month}-${day}T${hours}:${minutes}`; | |
| } | |
| /** | |
| * Create a note file for a placemark (note: you can customize the properties as needed) | |
| */ | |
| function createNote(placemark, id) { | |
| const filename = `${FILE_PREFIX} - ${padId(id)}.md`; | |
| const filepath = path.join(OUTPUT_DIR, filename); | |
| const timestamp = getTimestamp(); | |
| const content = `--- | |
| created: ${timestamp} | |
| updated: ${timestamp} | |
| coordinates: | |
| - "${placemark.latitude}" | |
| - "${placemark.longitude}" | |
| color: blue | |
| icon: landmark | |
| tags: | |
| - cameras | |
| --- | |
| `; | |
| fs.writeFileSync(filepath, content, 'utf8'); | |
| console.log(`Created: ${filename} (Lat: ${placemark.latitude}, Lon: ${placemark.longitude})`); | |
| } | |
| /** | |
| * Main function | |
| */ | |
| function main() { | |
| console.log('Processing KML files...\n'); | |
| // Get all KML files | |
| const kmlFiles = getKMLFiles(); | |
| if (kmlFiles.length === 0) { | |
| console.log('No KML files found in the current directory.'); | |
| return; | |
| } | |
| console.log(`Found ${kmlFiles.length} KML file(s):\n${kmlFiles.map(f => ` - ${f}`).join('\n')}\n`); | |
| // Get starting ID | |
| let currentId = getNextId(); | |
| console.log(`Starting from ID: ${padId(currentId)}\n`); | |
| // Process each KML file | |
| let totalPlacemarks = 0; | |
| const processedFiles = []; | |
| for (const kmlFile of kmlFiles) { | |
| console.log(`Processing: ${kmlFile}`); | |
| const placemarks = parsePlacemarks(kmlFile); | |
| console.log(` Found ${placemarks.length} placemark(s) with PlacemarkStyle`); | |
| for (const placemark of placemarks) { | |
| createNote(placemark, currentId); | |
| currentId++; | |
| totalPlacemarks++; | |
| } | |
| processedFiles.push(kmlFile); | |
| console.log(''); | |
| } | |
| console.log(`\nDone! Created ${totalPlacemarks} note(s).`); | |
| // Delete processed KML files | |
| if (processedFiles.length > 0) { | |
| console.log('\nDeleting processed KML files...'); | |
| for (const kmlFile of processedFiles) { | |
| try { | |
| fs.unlinkSync(kmlFile); | |
| console.log(` Deleted: ${kmlFile}`); | |
| } catch (err) { | |
| console.error(` Error deleting ${kmlFile}: ${err.message}`); | |
| } | |
| } | |
| } | |
| } | |
| // Run the script | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment