|
- <div class="container mt-4">
- <nav aria-label="breadcrumb" class="d-print-none">
- <ol class="breadcrumb">
- <li class="breadcrumb-item"><a href="/territories">Territories</a></li>
- <li class="breadcrumb-item active" aria-current="page"><%= Server.HTMLEncode(TerritoryController.territory.Name) %></li>
- </ol>
- </nav>
-
- <% Flash().ShowSuccessIfPresent %>
- <% Flash().ShowErrorsIfPresent %>
-
- <div class="card">
- <div class="card-header d-flex justify-content-between align-items-center">
- <h2 class="mb-0"><%= Server.HTMLEncode(TerritoryController.territory.Name) %></h2>
- <div class="d-print-none">
- <button type="button" class="btn btn-secondary" onclick="printTerritory()"><i class="bi bi-printer"></i> Print</button>
- <a href="/territories/<%= TerritoryController.territory.Id %>/edit" class="btn btn-warning">Edit</a>
- <form method="post" action="/territories/<%= TerritoryController.territory.Id %>/delete" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this territory?');">
- <button type="submit" class="btn btn-danger">Delete</button>
- </form>
- </div>
- </div>
- <div class="card-body">
- <div class="row">
- <div class="col-md-4 territory-details">
- <dl>
- <dt>ID</dt>
- <dd><%= TerritoryController.territory.Id %></dd>
-
- <dt>Name</dt>
- <dd><%= Server.HTMLEncode(TerritoryController.territory.Name) %></dd>
-
- <dt>Description</dt>
- <dd><%= Server.HTMLEncode(TerritoryController.territory.Description & "") %></dd>
- </dl>
- </div>
- <div class="col-md-8 map-container">
- <div id="map" style="height: 400px; width: 100%; border-radius: 8px;"></div>
- </div>
- </div>
- </div>
- </div>
-
- <div class="mt-3 d-print-none">
- <a href="/territories" class="btn btn-secondary">Back to List</a>
- </div>
- </div>
-
- <style>
- @media print {
- /* Hide navigation and non-essential elements */
- .d-print-none,
- nav.navbar,
- footer,
- .breadcrumb {
- display: none !important;
- }
-
- /* Set page size and margins */
- @page {
- size: letter portrait;
- margin: 0.5in;
- }
-
- /* Reset body for print */
- body {
- margin: 0 !important;
- padding: 0 !important;
- }
-
- .container {
- max-width: 100% !important;
- padding: 0 !important;
- margin: 0 !important;
- }
-
- .card {
- border: none !important;
- box-shadow: none !important;
- }
-
- .card-header {
- background-color: transparent !important;
- border-bottom: 2px solid #000 !important;
- padding: 0 0 10px 0 !important;
- margin-bottom: 10px !important;
- }
-
- .card-body {
- padding: 0 !important;
- }
-
- /* Territory title styling for print */
- h2 {
- font-size: 24pt !important;
- margin: 0 !important;
- text-align: center !important;
- }
-
- /* Hide details section in print, show only title and map */
- .territory-details {
- display: none !important;
- }
-
- /* Map takes 3/4 of 8.5x11 page (11in - 1in margins = 10in, 3/4 = 7.5in) */
- #map {
- height: 7.5in !important;
- width: 100% !important;
- page-break-inside: avoid;
- border: 1px solid #ccc !important;
- }
-
- .map-container {
- flex: 0 0 100% !important;
- max-width: 100% !important;
- }
-
- .row {
- display: block !important;
- }
-
- /* Ensure text is black for printing */
- body, .card-body, dt, dd, h2 {
- color: #000 !important;
- }
- }
- </style>
-
- <%
- Dim mapProvider, googleMapsKey, mapTilerKey, mapTilerStyle, mapTilerSdkJsUrl, mapTilerSdkCssUrl
- mapProvider = LCase(Trim(GetAppSetting("MapProvider") & ""))
- If mapProvider <> "maptiler" Then mapProvider = "google"
-
- googleMapsKey = Trim(GetAppSetting("GoogleMapsApiKey") & "")
- mapTilerKey = Trim(GetAppSetting("MapTilerApiKey") & "")
- mapTilerStyle = Trim(GetAppSetting("MapTilerStyle") & "")
- If mapTilerStyle = "" Or LCase(mapTilerStyle) = "nothing" Then mapTilerStyle = "streets-v2"
-
- mapTilerSdkJsUrl = Trim(GetAppSetting("MapTilerSdkJsUrl") & "")
- mapTilerSdkCssUrl = Trim(GetAppSetting("MapTilerSdkCssUrl") & "")
- If mapTilerSdkJsUrl = "" Or LCase(mapTilerSdkJsUrl) = "nothing" Then mapTilerSdkJsUrl = "https://cdn.maptiler.com/maptiler-sdk-js/v3.0.0/maptiler-sdk.umd.min.js"
- If mapTilerSdkCssUrl = "" Or LCase(mapTilerSdkCssUrl) = "nothing" Then mapTilerSdkCssUrl = "https://cdn.maptiler.com/maptiler-sdk-js/v3.0.0/maptiler-sdk.css"
- %>
-
- <%
- Dim coordsJson
- coordsJson = Trim(TerritoryController.territory.Coordinates & "")
- If coordsJson = "" Then coordsJson = "[]"
-
- ' Build street names array for JavaScript
- Dim streetIter, streetName, streetsJson, isFirst
- streetsJson = "["
- isFirst = True
- If Not TerritoryController.territoryStreets Is Nothing Then
- Set streetIter = TerritoryController.territoryStreets.Iterator()
- Do While streetIter.HasNext()
- streetName = streetIter.GetNext()
- If Not isFirst Then streetsJson = streetsJson & ","
- streetsJson = streetsJson & """" & Replace(streetName, """", "\""") & """"
- isFirst = False
- Loop
- End If
- streetsJson = streetsJson & "]"
-
- ' Get border streets from the Description field
- Dim borderStreets
- borderStreets = Trim(TerritoryController.territory.Description & "")
- %>
- <script>
- var territoryCoordinates = <%= coordsJson %>;
- var territoryName = "<%= Replace(TerritoryController.territory.Name, """", "\""") %>";
- var territoryBorderStreets = "<%= Replace(Replace(borderStreets, """", "\"""), vbCrLf, ", ") %>";
- var territoryStreets = <%= streetsJson %>;
- var mapProvider = "<%= Replace(mapProvider, """", "\""") %>";
- var googleMapsKey = "<%= Replace(googleMapsKey, """", "\""") %>";
- var mapTilerKey = "<%= Replace(mapTilerKey, """", "\""") %>";
- var mapTilerStyle = "<%= Replace(mapTilerStyle, """", "\""") %>";
- </script>
- <% If mapProvider = "maptiler" Then %>
- <link rel="stylesheet" href="<%= Server.HTMLEncode(mapTilerSdkCssUrl) %>" />
- <script src="<%= Server.HTMLEncode(mapTilerSdkJsUrl) %>"></script>
- <% Else %>
- <script src="https://maps.googleapis.com/maps/api/js?key=<%= Server.HTMLEncode(googleMapsKey) %>&callback=initMap" async defer></script>
- <% End If %>
- <script>
- var map, polygon;
- var mapIsReady = false;
-
- function setMapMessage(message) {
- var mapEl = document.getElementById('map');
- if (mapEl) {
- mapEl.innerHTML = '<div class="alert alert-info">' + message + '</div>';
- }
- }
-
- function getLatLng(coord) {
- var lat, lng;
- if (coord && typeof coord === 'object' && (coord.lat !== undefined || coord.lng !== undefined)) {
- lat = parseFloat(coord.lat);
- lng = parseFloat(coord.lng);
- } else if (Array.isArray(coord) && coord.length >= 2) {
- var a = parseFloat(coord[0]);
- var b = parseFloat(coord[1]);
- if (!isNaN(a) && !isNaN(b)) {
- if (Math.abs(a) > 90 && Math.abs(b) <= 90) {
- lng = a;
- lat = b;
- } else {
- lat = a;
- lng = b;
- }
- }
- }
- return { lat: lat, lng: lng };
- }
-
- function initMap() {
- try {
- var coords = territoryCoordinates;
-
- if (!coords || !Array.isArray(coords) || coords.length === 0) {
- setMapMessage('No coordinates available for this territory.');
- return;
- }
-
- if (mapProvider === 'maptiler') {
- if (!mapTilerKey || mapTilerKey === 'nothing') {
- setMapMessage('MapTiler API key is missing.');
- return;
- }
- if (typeof maptilersdk === 'undefined') {
- setMapMessage('MapTiler SDK failed to load.');
- return;
- }
-
- maptilersdk.config.apiKey = mapTilerKey;
-
- var polygonCoords = coords.map(function(coord) {
- var ll = getLatLng(coord);
- return [ll.lng, ll.lat];
- }).filter(function(p) {
- return isFinite(p[0]) && isFinite(p[1]);
- });
-
- if (polygonCoords.length === 0) {
- setMapMessage('No valid coordinates available for this territory.');
- return;
- }
-
- if (polygonCoords.length > 0) {
- var firstCoord = polygonCoords[0];
- var lastCoord = polygonCoords[polygonCoords.length - 1];
- if (firstCoord[0] !== lastCoord[0] || firstCoord[1] !== lastCoord[1]) {
- polygonCoords.push([firstCoord[0], firstCoord[1]]);
- }
- }
-
- var bounds = new maptilersdk.LngLatBounds();
- polygonCoords.forEach(function(coord) {
- bounds.extend(coord);
- });
-
- var styleUrl = 'https://api.maptiler.com/maps/' + encodeURIComponent(mapTilerStyle) + '/style.json?key=' + encodeURIComponent(mapTilerKey);
-
- map = new maptilersdk.Map({
- container: 'map',
- style: styleUrl,
- center: bounds.getCenter(),
- zoom: 14,
- preserveDrawingBuffer: true
- });
-
- map.on('styleimagemissing', function(e) {
- if (map.hasImage(e.id)) return;
- var emptyCanvas = document.createElement('canvas');
- emptyCanvas.width = 1;
- emptyCanvas.height = 1;
- map.addImage(e.id, emptyCanvas);
- });
-
- map.on('load', function() {
- map.addSource('territory', {
- type: 'geojson',
- data: {
- type: 'Feature',
- geometry: {
- type: 'Polygon',
- coordinates: [polygonCoords]
- }
- }
- });
-
- map.addLayer({
- id: 'territory-fill',
- type: 'fill',
- source: 'territory',
- paint: {
- 'fill-color': '#ff0000',
- 'fill-opacity': 0.35
- }
- });
-
- map.addLayer({
- id: 'territory-line',
- type: 'line',
- source: 'territory',
- paint: {
- 'line-color': '#ff0000',
- 'line-width': 2
- }
- });
-
- map.fitBounds(bounds, { padding: 20 });
- });
-
- map.on('idle', function() {
- mapIsReady = true;
- });
-
- return;
- }
-
- // Convert coordinates to Google Maps format
- var polygonCoords = coords.map(function(coord) {
- var ll = getLatLng(coord);
- return { lat: ll.lat, lng: ll.lng };
- }).filter(function(p) {
- return isFinite(p.lat) && isFinite(p.lng);
- });
-
- if (polygonCoords.length === 0) {
- setMapMessage('No valid coordinates available for this territory.');
- return;
- }
-
- // Calculate bounds to center the map
- var bounds = new google.maps.LatLngBounds();
- polygonCoords.forEach(function(coord) {
- bounds.extend(coord);
- });
-
- map = new google.maps.Map(document.getElementById('map'), {
- zoom: 14,
- center: bounds.getCenter(),
- mapTypeId: 'roadmap'
- });
-
- // Draw the polygon
- polygon = new google.maps.Polygon({
- paths: polygonCoords,
- strokeColor: '#FF0000',
- strokeOpacity: 0.8,
- strokeWeight: 2,
- fillColor: '#FF0000',
- fillOpacity: 0.35
- });
-
- polygon.setMap(map);
- map.fitBounds(bounds);
- } catch (e) {
- setMapMessage('Map error: ' + e.message);
- }
- }
-
- var printWindow = null;
-
- function printTerritory() {
- var coords = territoryCoordinates;
- if (!coords || !Array.isArray(coords) || coords.length === 0) {
- alert('No coordinates available to print.');
- return;
- }
-
- if (mapProvider === 'maptiler') {
-
- function openPrintWindowShell() {
- // Build street lists HTML
- var borderStreetsHtml = '';
- if (territoryBorderStreets && territoryBorderStreets.trim() !== '') {
- borderStreetsHtml = '<div class="streets-section"><strong>Border Streets:</strong> ' + territoryBorderStreets + '</div>';
- }
-
- var insideStreetsHtml = '';
- if (territoryStreets && territoryStreets.length > 0) {
- insideStreetsHtml = '<div class="streets-section"><strong>Streets Inside:</strong> ' + territoryStreets.join(', ') + '</div>';
- }
-
- var printContent = '<!DOCTYPE html>' +
- '<html><head><title>Territory: ' + territoryName + '</title>' +
- '<style>' +
- '@page { size: letter portrait; margin: 0.25in; }' +
- 'body { margin: 0; padding: 0; font-family: Arial, sans-serif; }' +
- '.header { text-align: center; border-bottom: 2px solid #000; padding-bottom: 5px; margin-bottom: 10px; }' +
- '.header h1 { margin: 0; font-size: 20pt; }' +
- '.streets-section { font-size: 10pt; margin: 5px 0; text-align: left; }' +
- '.map-container { text-align: center; min-height: 200px; }' +
- '.map-container img { max-width: 100%; height: auto; max-height: 7in; }' +
- '.map-link { display: inline-block; margin-top: 10px; font-size: 12px; word-break: break-all; }' +
- '.print-btn { margin-top: 15px; padding: 10px 30px; font-size: 16px; cursor: pointer; }' +
- '.loading { color: #666; font-size: 14px; }' +
- '@media print { .no-print { display: none !important; } }' +
- '</style></head><body>' +
- '<div class="header"><h1>' + territoryName + '</h1></div>' +
- borderStreetsHtml +
- insideStreetsHtml +
- '<div class="map-container">' +
- '<div class="loading">Preparing map for print...</div>' +
- '<img id="print-map-img" src="" alt="Territory Map" style="display:none;">' +
- '<div class="map-link no-print"><a id="print-map-link" href="#" target="_blank" rel="noopener">Open image in new tab</a></div>' +
- '</div>' +
- '<div class="no-print" style="text-align: center; margin-top: 15px;">' +
- '<button class="print-btn" onclick="window.print();">Print</button> ' +
- '<button class="print-btn" onclick="window.close();">Close</button>' +
- '</div>' +
- '</body></html>';
-
- printWindow = window.open('', '_blank', 'width=900,height=1000');
- printWindow.document.write(printContent);
- printWindow.document.close();
- }
-
- function setPrintWindowImage(src) {
- if (!printWindow || printWindow.closed) {
- alert('Print window was blocked. Please allow popups and try again.');
- return;
- }
-
- var attempts = 0;
- function trySet() {
- attempts = attempts + 1;
- var doc = printWindow.document;
- if (!doc) {
- if (attempts < 20) return setTimeout(trySet, 100);
- alert('Print window did not finish loading.');
- return;
- }
- var img = doc.getElementById('print-map-img');
- var loading = doc.querySelector('.loading');
- var link = doc.getElementById('print-map-link');
- if (!img) {
- if (attempts < 20) return setTimeout(trySet, 100);
- alert('Print image element not found.');
- return;
- }
- if (loading) loading.style.display = 'none';
- img.style.display = 'block';
- img.src = src;
- if (link) link.href = src;
- }
- trySet();
- }
-
- openPrintWindowShell();
-
- function loadImage(url) {
- return new Promise(function(resolve, reject) {
- var img = new Image();
- img.crossOrigin = 'anonymous';
- img.onload = function() { resolve(img); };
- img.onerror = function() { reject(url); };
- img.src = url;
- });
- }
-
- function renderMapTilerImage() {
- return new Promise(function(resolve, reject) {
- if (!mapTilerKey || mapTilerKey === 'nothing') {
- reject('MapTiler API key is missing.');
- return;
- }
-
- var tileCoordSize = 512;
- if (map && typeof map.getStyle === 'function') {
- var styleObj = map.getStyle();
- if (styleObj && styleObj.sources) {
- for (var srcKey in styleObj.sources) {
- if (styleObj.sources.hasOwnProperty(srcKey)) {
- var src = styleObj.sources[srcKey];
- if (src && src.tileSize) {
- tileCoordSize = src.tileSize;
- break;
- }
- }
- }
- }
- }
-
- function buildTileUrl(z, x, y) {
- return 'https://api.maptiler.com/maps/' +
- encodeURIComponent(mapTilerStyle) + '/' +
- z + '/' + x + '/' + y + '.png?key=' +
- encodeURIComponent(mapTilerKey);
- }
-
- function buildImage(tileImageSize) {
- function lngToWorldX(lng, zoom) {
- var scale = tileCoordSize * Math.pow(2, zoom);
- return (lng + 180) / 360 * scale;
- }
-
- function latToWorldY(lat, zoom) {
- var rad = lat * Math.PI / 180;
- var scale = tileCoordSize * Math.pow(2, zoom);
- return (1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2 * scale;
- }
-
- var minLat = Infinity, maxLat = -Infinity;
- var minLng = Infinity, maxLng = -Infinity;
- coords.forEach(function(coord) {
- var ll = getLatLng(coord);
- var lat = ll.lat;
- var lng = ll.lng;
- if (!isFinite(lat) || !isFinite(lng)) return;
- if (lat < minLat) minLat = lat;
- if (lat > maxLat) maxLat = lat;
- if (lng < minLng) minLng = lng;
- if (lng > maxLng) maxLng = lng;
- });
-
- if (!isFinite(minLat) || !isFinite(minLng) || !isFinite(maxLat) || !isFinite(maxLng)) {
- reject('No valid coordinates available to print.');
- return;
- }
-
- var centerLat = (minLat + maxLat) / 2;
- var centerLng = (minLng + maxLng) / 2;
-
- var isWide = (maxLng - minLng) > (maxLat - minLat) * 1.2;
- var baseWidth = isWide ? 800 : 640;
- var baseHeight = isWide ? 640 : 800;
- var outputScale = tileImageSize > 0 ? (tileImageSize / tileCoordSize) : 1;
- var width = baseWidth * outputScale;
- var height = baseHeight * outputScale;
-
- var x1 = lngToWorldX(minLng, 0);
- var x2 = lngToWorldX(maxLng, 0);
- var y1 = latToWorldY(maxLat, 0);
- var y2 = latToWorldY(minLat, 0);
- var spanX0 = Math.abs(x2 - x1);
- var spanY0 = Math.abs(y2 - y1);
- var paddingFactor = 1.02;
- var zoomX = spanX0 > 0 ? Math.log2(baseWidth / (spanX0 * paddingFactor)) : 20;
- var zoomY = spanY0 > 0 ? Math.log2(baseHeight / (spanY0 * paddingFactor)) : 20;
- var zoom = Math.min(zoomX, zoomY);
- if (!isFinite(zoom)) zoom = 1;
- if (zoom < 1) zoom = 1;
- if (zoom > 20) zoom = 20;
-
- var zInt = Math.floor(zoom);
- var zScale = Math.pow(2, zoom - zInt);
- var worldCenterXint = lngToWorldX(centerLng, zInt);
- var worldCenterYint = latToWorldY(centerLat, zInt);
- var worldWidth = width / (outputScale * zScale);
- var worldHeight = height / (outputScale * zScale);
- var topLeftXint = worldCenterXint - worldWidth / 2;
- var topLeftYint = worldCenterYint - worldHeight / 2;
- var topLeftX = topLeftXint * outputScale * zScale;
- var topLeftY = topLeftYint * outputScale * zScale;
-
- var n = Math.pow(2, zInt);
- var tileXStart = Math.floor(topLeftXint / tileCoordSize);
- var tileYStart = Math.floor(topLeftYint / tileCoordSize);
- var tileXEnd = Math.floor((topLeftXint + worldWidth) / tileCoordSize);
- var tileYEnd = Math.floor((topLeftYint + worldHeight) / tileCoordSize);
-
- var promises = [];
- var tiles = [];
- var tileX, tileY;
- for (tileY = tileYStart; tileY <= tileYEnd; tileY++) {
- if (tileY < 0 || tileY >= n) continue;
- for (tileX = tileXStart; tileX <= tileXEnd; tileX++) {
- var wrappedX = ((tileX % n) + n) % n;
- var url = buildTileUrl(zInt, wrappedX, tileY);
- (function(x, y, u) {
- var p = loadImage(u).then(function(img) {
- tiles.push({ x: x, y: y, img: img });
- });
- promises.push(p);
- })(tileX, tileY, url);
- }
- }
-
- Promise.all(promises).then(function() {
- var canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- var ctx = canvas.getContext('2d');
-
- var tileDrawSize = tileCoordSize * zScale * outputScale;
- tiles.forEach(function(t) {
- var dx = (t.x * tileCoordSize * zScale * outputScale) - topLeftX;
- var dy = (t.y * tileCoordSize * zScale * outputScale) - topLeftY;
- ctx.drawImage(t.img, dx, dy, tileDrawSize, tileDrawSize);
- });
-
- if (coords.length > 1) {
- ctx.save();
- ctx.strokeStyle = 'rgba(255,0,0,0.8)';
- ctx.fillStyle = 'rgba(255,0,0,0.25)';
- ctx.lineWidth = 3;
- ctx.beginPath();
- coords.forEach(function(coord, idx) {
- var ll = getLatLng(coord);
- var lat = ll.lat;
- var lng = ll.lng;
- if (!isFinite(lat) || !isFinite(lng)) return;
- var px = (lngToWorldX(lng, zInt) * zScale * outputScale) - topLeftX;
- var py = (latToWorldY(lat, zInt) * zScale * outputScale) - topLeftY;
- if (idx === 0) {
- ctx.moveTo(px, py);
- } else {
- ctx.lineTo(px, py);
- }
- });
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- ctx.restore();
- }
-
- var dataUrl = "";
- try {
- dataUrl = canvas.toDataURL('image/png');
- } catch (e) {
- reject('Unable to generate print image.');
- return;
- }
-
- if (!dataUrl || dataUrl.length < 1000) {
- reject('Generated print image is empty.');
- return;
- }
-
- resolve(dataUrl);
- }).catch(function() {
- reject('Failed to load map tiles for printing.');
- });
- }
-
- var testUrl = buildTileUrl(0, 0, 0);
- loadImage(testUrl).then(function(img) {
- var tileImageSize = (img && img.width) ? img.width : 256;
- buildImage(tileImageSize);
- }).catch(function() {
- buildImage(256);
- });
- });
- }
-
- renderMapTilerImage().then(function(dataUrl) {
- setPrintWindowImage(dataUrl);
- }).catch(function(errMsg) {
- alert(errMsg);
- });
- return;
- }
-
- // Build polygon path for Static Maps API
- var pathPoints = coords.map(function(coord) {
- var ll = getLatLng(coord);
- if (!isFinite(ll.lat) || !isFinite(ll.lng)) return null;
- return ll.lat + ',' + ll.lng;
- }).filter(function(p) { return p !== null; });
- // Close the polygon
- pathPoints.push(pathPoints[0]);
- var pathStr = pathPoints.join('|');
-
- // Calculate bounding box to determine optimal zoom
- var minLat = Infinity, maxLat = -Infinity;
- var minLng = Infinity, maxLng = -Infinity;
- coords.forEach(function(coord) {
- var ll = getLatLng(coord);
- var lat = ll.lat;
- var lng = ll.lng;
- if (!isFinite(lat) || !isFinite(lng)) return;
- if (lat < minLat) minLat = lat;
- if (lat > maxLat) maxLat = lat;
- if (lng < minLng) minLng = lng;
- if (lng > maxLng) maxLng = lng;
- });
-
- var centerLat = (minLat + maxLat) / 2;
- var centerLng = (minLng + maxLng) / 2;
-
- // Calculate the span of the polygon
- var latSpan = maxLat - minLat;
- var lngSpan = maxLng - minLng;
-
- // Calculate optimal zoom level to maximize polygon size
- // Map dimensions: 640x800 (width x height for portrait letter)
- // Each zoom level doubles the scale
- var zoom = 20; // Start with max zoom
-
- // Degrees per pixel at zoom level 0 is approximately 360/256 for lng
- // For latitude it varies, but we use equirectangular approximation
- var mapWidth = 640;
- var mapHeight = 800;
-
- // Add 10% padding around the polygon
- var paddedLatSpan = latSpan * 1.1;
- var paddedLngSpan = lngSpan * 1.1;
-
- // Calculate zoom based on longitude span
- if (paddedLngSpan > 0) {
- var lngZoom = Math.floor(Math.log2(360 / paddedLngSpan * mapWidth / 256));
- if (lngZoom < zoom) zoom = lngZoom;
- }
-
- // Calculate zoom based on latitude span
- if (paddedLatSpan > 0) {
- var latZoom = Math.floor(Math.log2(180 / paddedLatSpan * mapHeight / 256));
- if (latZoom < zoom) zoom = latZoom;
- }
-
- // Clamp zoom between 1 and 20
- if (zoom < 1) zoom = 1;
- if (zoom > 20) zoom = 20;
-
- var staticMapUrl;
-
- if (mapProvider === 'maptiler') {
- if (!mapTilerKey || mapTilerKey === 'nothing') {
- alert('MapTiler API key is missing.');
- return;
- }
-
- var mapWidth = 640;
- var mapHeight = 800;
- var pathParam = 'stroke:#ff0000|width:3|fill:none|' + pathStr;
-
- staticMapUrl = 'https://api.maptiler.com/maps/' +
- encodeURIComponent(mapTilerStyle) +
- '/static/auto/' +
- mapWidth + 'x' + mapHeight + '@2x.png' +
- '?path=' + encodeURIComponent(pathParam) +
- '&key=' + encodeURIComponent(mapTilerKey);
- } else {
- // Build Google Static Maps URL with scale=2 for higher resolution (1280x1600 actual pixels)
- staticMapUrl = 'https://maps.googleapis.com/maps/api/staticmap?' +
- 'size=640x800' +
- '&scale=2' +
- '¢er=' + centerLat + ',' + centerLng +
- '&zoom=' + zoom +
- '&maptype=roadmap' +
- '&path=color:0xFF0000CC|weight:3|fillcolor:0xFF000044|' + pathStr +
- '&key=' + encodeURIComponent(googleMapsKey);
- }
-
- // Open print window with static map - landscape for wider polygons, portrait for taller
- var isWide = lngSpan > latSpan * 1.2;
- var pageSize = isWide ? 'letter landscape' : 'letter portrait';
- var imgMaxHeight = isWide ? '5in' : '7in';
-
- // Build street lists HTML
- var borderStreetsHtml = '';
- if (territoryBorderStreets && territoryBorderStreets.trim() !== '') {
- borderStreetsHtml = '<div class="streets-section"><strong>Border Streets:</strong> ' + territoryBorderStreets + '</div>';
- }
-
- var insideStreetsHtml = '';
- if (territoryStreets && territoryStreets.length > 0) {
- insideStreetsHtml = '<div class="streets-section"><strong>Streets Inside:</strong> ' + territoryStreets.join(', ') + '</div>';
- }
-
- var printContent = '<!DOCTYPE html>' +
- '<html><head><title>Territory: ' + territoryName + '</title>' +
- '<style>' +
- '@page { size: ' + pageSize + '; margin: 0.25in; }' +
- 'body { margin: 0; padding: 0; font-family: Arial, sans-serif; }' +
- '.header { text-align: center; border-bottom: 2px solid #000; padding-bottom: 5px; margin-bottom: 10px; }' +
- '.header h1 { margin: 0; font-size: 20pt; }' +
- '.streets-section { font-size: 10pt; margin: 5px 0; text-align: left; }' +
- '.map-container { text-align: center; }' +
- '.map-container img { max-width: 100%; height: auto; max-height: ' + imgMaxHeight + '; }' +
- '.print-btn { margin-top: 15px; padding: 10px 30px; font-size: 16px; cursor: pointer; }' +
- '@media print { .no-print { display: none !important; } }' +
- '</style></head><body>' +
- '<div class="header"><h1>' + territoryName + '</h1></div>' +
- borderStreetsHtml +
- insideStreetsHtml +
- '<div class="map-container">' +
- '<img src="' + staticMapUrl + '" alt="Territory Map" onerror="document.body.innerHTML=\'<p>Error loading map. Please try again.</p>\';">' +
- '</div>' +
- '<div class="no-print" style="text-align: center; margin-top: 15px;">' +
- '<button class="print-btn" onclick="window.print();">Print</button> ' +
- '<button class="print-btn" onclick="window.close();">Close</button>' +
- '</div>' +
- '</body></html>';
-
- printWindow = window.open('', '_blank', 'width=900,height=1000');
- printWindow.document.write(printContent);
- printWindow.document.close();
- }
-
- if (mapProvider === 'maptiler') {
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initMap);
- } else {
- initMap();
- }
- setTimeout(function() {
- if (!map) {
- setMapMessage('Map failed to load.');
- }
- }, 2000);
- } else {
- setTimeout(function() {
- if (typeof google === 'undefined') {
- setMapMessage('Google Maps failed to load.');
- }
- }, 2000);
- }
- </script>
|