{"id":18,"date":"2026-03-11T21:11:52","date_gmt":"2026-03-11T21:11:52","guid":{"rendered":"https:\/\/map.scubaviz.us\/?page_id=18"},"modified":"2026-03-11T23:36:41","modified_gmt":"2026-03-11T23:36:41","slug":"layer-updater","status":"publish","type":"page","link":"https:\/\/map.scubaviz.us\/index.php\/layer-updater\/","title":{"rendered":"Map Layer Maker"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>ScubaViz GeoJSON Converter<\/title>\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n    <!-- Library for converting Shapefile ZIPs to GeoJSON -->\n    <script src=\"https:\/\/unpkg.com\/shpjs@latest\/dist\/shp.js\"><\/script>\n    <!-- Library for converting KML\/GPX to GeoJSON -->\n    <script src=\"https:\/\/unpkg.com\/@tmcw\/togeojson@5.8.1\/dist\/togeojson.umd.js\"><\/script>\n<\/head>\n<body class=\"bg-gray-100 min-h-screen p-8 font-sans text-gray-800\">\n\n    <div class=\"max-w-3xl mx-auto bg-white rounded-lg shadow-md p-6\">\n        <h2 class=\"text-2xl font-bold mb-4 text-blue-900 border-b pb-2\">ScubaViz Vector Data Converter<\/h2>\n        \n        <div class=\"mb-6 text-sm text-gray-700 bg-blue-50 border-l-4 border-blue-500 p-4\">\n            <h3 class=\"font-bold text-blue-900 mb-2\">How to update Map Overlays:<\/h3>\n            <ol class=\"list-decimal pl-5 space-y-2\">\n                <li>Convert your Shapefiles (must be in a <strong>.zip<\/strong> containing .shp, .shx, .dbf, .prj), <strong>.kml<\/strong>, or <strong>.gpx<\/strong> to standard EPSG:4326\/WGS84 GeoJSON format using this tool.<\/li>\n                <li>Select the target output name (exactly <strong>reefs.geojson<\/strong> or <strong>wrecks.geojson<\/strong>) and click Download.<\/li>\n                <li>Upload the downloaded file to your server at exactly: <code class=\"bg-gray-200 px-1 py-0.5 rounded text-red-600 font-mono\">C:\\map.scubaviz.us\\data\\vectors\\<\/code><\/li>\n                <li>When the corresponding checkbox is active on the map page, the map will automatically fetch and draw the updated data.<\/li>\n            <\/ol>\n        <\/div>\n\n        <div class=\"mb-6 space-y-4\">\n            <!-- File Input -->\n            <div>\n                <label class=\"block font-bold mb-1\">1. Select Input File (.zip, .kml, .gpx)<\/label>\n                <input type=\"file\" id=\"fileInput\" accept=\".zip,.kml,.gpx\" class=\"block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 border border-gray-300 rounded p-2\"\/>\n            <\/div>\n\n            <!-- Target Name Selection -->\n            <div>\n                <label class=\"block font-bold mb-1\">2. Select Output Layer Name<\/label>\n                <select id=\"outputName\" class=\"w-full border border-gray-300 rounded p-2 bg-gray-50\">\n                    <option value=\"reefs.geojson\">reefs.geojson<\/option>\n                    <option value=\"wrecks.geojson\">wrecks.geojson<\/option>\n                    <option value=\"custom\">Custom Name&#8230;<\/option>\n                <\/select>\n                <input type=\"text\" id=\"customName\" placeholder=\"custom_layer.geojson\" class=\"w-full border border-gray-300 rounded p-2 mt-2 hidden bg-gray-50\" \/>\n            <\/div>\n\n            <!-- Action Button -->\n            <button id=\"convertBtn\" class=\"w-full bg-blue-600 text-white font-bold py-3 px-4 rounded hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed\">\n                Convert Data\n            <\/button>\n        <\/div>\n\n        <!-- Status & Preview -->\n        <div id=\"statusArea\" class=\"hidden\">\n            <h3 class=\"font-bold text-lg mb-2 text-green-700\" id=\"statusMessage\">Conversion Successful!<\/h3>\n            <button id=\"downloadBtn\" class=\"w-full bg-green-600 text-white font-bold py-3 px-4 rounded hover:bg-green-700 transition mb-4\">\n                Download GeoJSON\n            <\/button>\n            <label class=\"block font-bold mb-1 text-sm\">GeoJSON Preview (First 1000 characters):<\/label>\n            <textarea id=\"previewArea\" readonly class=\"w-full h-48 p-3 text-xs font-mono bg-gray-900 text-green-400 rounded border border-gray-700\"><\/textarea>\n        <\/div>\n        \n        <div id=\"errorArea\" class=\"hidden mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded\"><\/div>\n    <\/div>\n\n    <script>\n        let currentGeoJson = null;\n\n        const fileInput = document.getElementById('fileInput');\n        const outputNameSelect = document.getElementById('outputName');\n        const customNameInput = document.getElementById('customName');\n        const convertBtn = document.getElementById('convertBtn');\n        const downloadBtn = document.getElementById('downloadBtn');\n        const statusArea = document.getElementById('statusArea');\n        const errorArea = document.getElementById('errorArea');\n        const previewArea = document.getElementById('previewArea');\n\n        \/\/ Toggle custom name input\n        outputNameSelect.addEventListener('change', (e) => {\n            if (e.target.value === 'custom') {\n                customNameInput.classList.remove('hidden');\n            } else {\n                customNameInput.classList.add('hidden');\n            }\n        });\n\n        convertBtn.addEventListener('click', async () => {\n            const file = fileInput.files[0];\n            if (!file) {\n                showError(\"Please select a file first.\");\n                return;\n            }\n\n            hideStatus();\n            convertBtn.disabled = true;\n            convertBtn.textContent = \"Processing...\";\n\n            const extension = file.name.split('.').pop().toLowerCase();\n\n            try {\n                if (extension === 'zip') {\n                    \/\/ Handle Shapefile ZIP via shpjs\n                    const arrayBuffer = await file.arrayBuffer();\n                    currentGeoJson = await shp(arrayBuffer);\n                    processSuccess();\n                } else if (extension === 'kml' || extension === 'gpx') {\n                    \/\/ Handle KML\/GPX via DOMParser and toGeoJSON\n                    const text = await file.text();\n                    const parser = new DOMParser();\n                    const xmlDoc = parser.parseFromString(text, \"text\/xml\");\n                    \n                    if (extension === 'kml') {\n                        currentGeoJson = toGeoJSON.kml(xmlDoc);\n                    } else {\n                        currentGeoJson = toGeoJSON.gpx(xmlDoc);\n                    }\n                    processSuccess();\n                } else {\n                    throw new Error(\"Unsupported file format. Please upload .zip (Shapefile), .kml, or .gpx\");\n                }\n            } catch (err) {\n                console.error(err);\n                showError(\"Conversion failed. Ensure your ZIP contains valid .shp\/.shx\/.dbf\/.prj files or your KML\/GPX is valid XML. Error: \" + err.message);\n            } finally {\n                convertBtn.disabled = false;\n                convertBtn.textContent = \"Convert Data\";\n            }\n        });\n\n        function processSuccess() {\n            if (!currentGeoJson) {\n                showError(\"Conversion resulted in empty data.\");\n                return;\n            }\n\n            \/\/ If it's an array of FeatureCollections (multiple shapefiles in one zip), flatten them into one FeatureCollection\n            if (Array.isArray(currentGeoJson)) {\n                let combinedFeatures = [];\n                currentGeoJson.forEach(fc => {\n                    if (fc.features) combinedFeatures = combinedFeatures.concat(fc.features);\n                });\n                currentGeoJson = {\n                    type: \"FeatureCollection\",\n                    features: combinedFeatures\n                };\n            }\n\n            const jsonString = JSON.stringify(currentGeoJson, null, 2);\n            previewArea.value = jsonString.substring(0, 1000) + (jsonString.length > 1000 ? \"\\n\\n... [TRUNCATED FOR PREVIEW]\" : \"\");\n            \n            errorArea.classList.add('hidden');\n            statusArea.classList.remove('hidden');\n        }\n\n        downloadBtn.addEventListener('click', () => {\n            if (!currentGeoJson) return;\n\n            let filename = outputNameSelect.value;\n            if (filename === 'custom') {\n                filename = customNameInput.value.trim() || 'custom_layer.geojson';\n                if (!filename.endsWith('.geojson')) filename += '.geojson';\n            }\n\n            const dataStr = \"data:text\/json;charset=utf-8,\" + encodeURIComponent(JSON.stringify(currentGeoJson));\n            const downloadAnchorNode = document.createElement('a');\n            downloadAnchorNode.setAttribute(\"href\", dataStr);\n            downloadAnchorNode.setAttribute(\"download\", filename);\n            document.body.appendChild(downloadAnchorNode); \/\/ required for firefox\n            downloadAnchorNode.click();\n            downloadAnchorNode.remove();\n        });\n\n        function showError(msg) {\n            errorArea.textContent = msg;\n            errorArea.classList.remove('hidden');\n            statusArea.classList.add('hidden');\n        }\n\n        function hideStatus() {\n            errorArea.classList.add('hidden');\n            statusArea.classList.add('hidden');\n        }\n    <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>ScubaViz GeoJSON Converter ScubaViz Vector Data Converter How to update Map Overlays: Convert your Shapefiles (must be in a .zip [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"pmpro_default_level":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-18","page","type-page","status-publish","hentry","pmpro-has-access"],"_links":{"self":[{"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/pages\/18","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/comments?post=18"}],"version-history":[{"count":6,"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/pages\/18\/revisions"}],"predecessor-version":[{"id":25,"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/pages\/18\/revisions\/25"}],"wp:attachment":[{"href":"https:\/\/map.scubaviz.us\/index.php\/wp-json\/wp\/v2\/media?parent=18"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}