', { 'class': 'beachForecastSidebarLinkTitle' }) .html(link.title) .appendTo($sidebarLinkAnchor); } } $sidebarLink.append($sidebarLinkAnchor); $sidebar.append($sidebarLink); }); $('#beachForecastContainer').prepend($sidebar); } } function checkKMLExpiration(jqXHR) { var serverTime = jqXHR.getResponseHeader("date"); //.toLowerCase(); var srfTime = jqXHR.getResponseHeader("last-modified"); //.toLowerCase(); log("Checking creation time " + srfTime); log("> against current time " + serverTime); var splitServerTime = serverTime.split(" ") var serverDate = new Date(splitServerTime[1] + " " + splitServerTime[2] + " " + splitServerTime[3] + " " + splitServerTime[4]); var serverEpoch = serverDate.getTime() / 1000.0; var splitSRFTime = srfTime.split(" ") var srfDate = new Date(splitSRFTime[1] + " " + splitSRFTime[2] + " " + splitSRFTime[3] + " " + splitSRFTime[4]); var srfEpoch = srfDate.getTime() / 1000.0; var diff = serverEpoch - srfEpoch; log("Expiration difference is " + (diff / 3600) + " hours"); return (diff / 3600) < 24 ? true : false; } function loadMap(validData) { log("Loading map (" + (validData?"enabled":"disabled") + ")"); $('#beachForecastMap').height(options.map.height); $('#beachForecastMapOL').empty(); // because CMS editor requires for empty tag var attribution = new ol.Attribution({ html: 'Tiles © ArcGIS' }); var beachPointStyle = new ol.style.Style({ image: new ol.style.Icon( /** @type {olx.style.IconOptions} */ ({ src: options.imagePath + options.map.marker.filename, anchor: [10,0], anchorXUnits: 'pixel', anchorYUnits: 'pixel', anchorOrigin: 'bottom-left' })) }); var beachPoints = []; if(validData) { $('#beachForecastMapLabel') .attr('src', options.siteImagePath + 'srfLabel.png') .show(); for (var i = 0; i < options.beachPoints.length; i++) { //var beachPointPos = [parseFloat(options.beachPoints[i][2]), parseFloat(options.beachPoints[i][1])]; var beachPoint = new ol.Feature({ geometry: new ol.geom.Point( ol.proj.fromLonLat([parseFloat(options.beachPoints[i][2]), parseFloat(options.beachPoints[i][1])]) ), name: options.beachPoints[i][0], id: options.beachPoints[i][0].toLowerCase().replace(/\s/g, ''), type: 'beachPoint', loc: [parseFloat(options.beachPoints[i][2]), parseFloat(options.beachPoints[i][1])] }); beachPoint.setStyle(beachPointStyle); beachPoints.push(beachPoint); } } /* var forecastLabel = function(opt_options) { var options = opt_options || {}; var $button = $('
').css({ 'background': '#f00' ,'width': 100 ,'height': 100 }) .text("BUTTON!"); ol.control.Control.call(this, { element: $button[0], target: options.target }); }; ol.inherits(forecastLabel, ol.control.Control);*/ var map = new ol.Map({/* controls: [ new forecastLabel(), ], controls: ol.control.defaults().extend([ new forecastLabel(), ]),*/ layers: [ new ol.layer.Tile({ source: new ol.source.XYZ({ attributions: [attribution], url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}?appid=45ad401c-fa23-4a10-8b2f-a7ad29a3e2a0" }) }), new ol.layer.Tile({ source: new ol.source.XYZ({ attributions: [attribution], url: "https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}?appid=45ad401c-fa23-4a10-8b2f-a7ad29a3e2a0" }) })/*, new ol.layer.Tile({ source: new ol.source.XYZ({ attributions: [attribution], url: "https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}?appid=45ad401c-fa23-4a10-8b2f-a7ad29a3e2a0" }) })*/, new ol.layer.Vector({ source: new ol.source.Vector({ url: options.kmlUrl, format: new ol.format.KML() }) }), new ol.layer.Vector({ source: new ol.source.Vector({ features: beachPoints }) }) ], target: $('#' + options.map.canvasId)[0], controls: ol.control.defaults({ attributionOptions: /** @type {olx.control.AttributionOptions} */ ({ collapsible: false }) }), logo: false, view: new ol.View({ center: ol.proj.fromLonLat([parseFloat(options.map.lon), parseFloat(options.map.lat)], 'EPSG:3857'), zoom: options.map.zoom }) }); if (otherSites) { $.each(otherSites, function (i, item) { adjacentSite = otherSites[i][0]; log(otherSites[i][0].toUpperCase() + " is an adjacent site."); adjacentKmlUrl = "/source/" + adjacentSite + "/beachforecast/SRF.kml"; log("Checking " + adjacentSite.toUpperCase() + " KML expiration"); $.ajax({ url: adjacentKmlUrl, async: false }).done(function(data, status, jqXHR) { if (checkKMLExpiration (jqXHR)) { log("Loading KML at /source/" + otherSites[i][0] + "/beachforecast/SRF.kml"); var otherSitesKML = new ol.layer.Vector({ source: new ol.source.Vector({ url: "/source/" + otherSites[i][0] + "/beachforecast/SRF.kml", format: new ol.format.KML() }) }); map.addLayer(otherSitesKML); document.getElementById(otherSites[i][1]).innerHTML = ""; } else { log("Invalid KML expiration"); } }).fail(function(x, status) { log (adjacentSite.toUpperCase() + " KML failed to load"); }); }); } map.on('pointermove', function(e) { var pixel = map.getEventPixel(e.originalEvent); var hit = map.hasFeatureAtPixel(pixel); map.getTarget().style.cursor = hit ? 'pointer' : ''; }); map.on('click', function(evt) { var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) { return feature; }); if (feature) { if (feature.get('type') === 'beachPoint') { displayForecast(feature.get('loc'), feature.get('id'), feature.get('name')); } else { // presume KML click var zoneDetails = '
National Weather Service '+siteTitle+'
'; zoneDetails += '
'+feature.get('description')+'
'; $('#riskDetailsModal').modal(); document.getElementById("riskDetailsModal").innerHTML = zoneDetails; } } }); if(!validData) { $('#beachForecastMapLabel') .attr('src', options.siteImagePath + 'srfLabel_expired.png') .show(); $('#beachForecastMapOL').css('opacity', .2); $('#riskDetails').html("Notice!
The beach foreach for this area is not valid or expired.") map.removeLayer(map.getLayers().item(2)); map.getInteractions().forEach(function(interaction) { map.removeInteraction(interaction); }, this); map.getControls().forEach(function(control) { map.removeControl(control); }, this); } } function displayForecast(loc, id, name) { log("Forecast for " + loc + ", " + id + " (" + name + ")"); $('#beachForecastOverlay') .click(function () { hideForecast(); }) .show(); $('#beachForecastOverlayContent').show(); var $forecast = $('#beachForecastTemplate'); // if different location than previous click if (id != currentForecastId) { currentForecastId = id; updateForecastMessage(0); // have to used secondary deferreds to because jQuery stops any one promise fails var $forecastDeferred = $.Deferred(); var $beachesDeferred = $.Deferred(); $.ajax({ url: '//forecast.weather.gov/MapClick.php', jsonp: 'callback', dataType: 'jsonp', data: { FcstType: 'json', lon: loc[0], lat: loc[1] } }).complete($forecastDeferred.resolve); $.ajax({ url: options.siteSourcePath + "/beaches.json", dataType: 'json' }).complete($beachesDeferred.resolve); $.when($forecastDeferred, $beachesDeferred) .done(function(forecast, beach) { if(forecast[1] == 'success') { forecast[0].done(function(data) { $forecast.find('#beachForecastTemplateWFO').text(data.productionCenter); $forecast.find('#beachForecastTemplateName').text(name); $forecast.find('#beachForecastTemplateUpdate').text(data.creationDateLocal); $forecast.find('#beachForecastTemplateExtendedContainer').html(buildForecastHTML(data)); }); if(beach[1] == 'success') { beach[0].done(function(data) { if(typeof data[id] !== 'undefined') { var beachData = data[id]; var $beachForecastTemplateSrfHTML = $forecast.find('#beachForecastTemplateSrf').append($('#beachForecastTemplateHTML-srf').clone()); $beachForecastTemplateSrfHTML.find('#beachForecastTemplateSrfSurf') .find('.beachForecastTemplateSrfTitle') .css({ 'background-color': rip[beachData.riprisk].color }) .end() .find('.beachForecastTemplateSrfLevel') .html($('#riskTable-' + beachData.riprisk + ' .riskTableLevel').html()) .end() .find('.beachForecastTemplateSrfContent') .html($('#riskTable-' + beachData.riprisk + ' .riskTableDescription').html()) .end() .find('.beachForecastTemplateSrfBackgrounds') .css({ 'border-color': rip[beachData.riprisk].color }); $beachForecastTemplateSrfHTML.find('#beachForecastTemplateSrfUV') .each(function() { var $template = $(this); if(typeof beachData.uvindex !== 'undefined') { $template .find('.beachForecastTemplateSrfTitle') .css({ 'background-color': uvindex[beachData.uvindex].color }) .end() .find('.beachForecastTemplateSrfLevel') .html($('#uvTable-' + beachData.uvindex + ' .uvTableRisk').html()) .end() .find('.beachForecastTemplateSrfContent') .html($('#uvTable-' + beachData.uvindex + ' .uvTableDescription').html()) .end() .find('.beachForecastTemplateSrfBackgrounds') .css({ 'border-color': uvindex[beachData.uvindex].color }); } else { $template.hide(); } }); $beachForecastTemplateSrfHTML.find('#beachForecastTemplateSrfTemperature') .each(function() { var $template = $(this); if(typeof beachData.surftemp !== 'undefined') { $template .find('.beachForecastTemplateSrfTitle') .css({ 'background-color': '#66CCFF' }) .end() .find('.beachForecastTemplateSrfContent') .html(beachData.surftemp) .end(); } else { $template.hide(); } }); $beachForecastTemplateSrfHTML.find('#beachForecastTemplateSrfQRCode').append($('', {src:options.siteImagePath + 'qrcode.png'})); } else { log("Cannot find beach information for " + id); } }); } updateForecastMessage(); $('#beachForecastTemplate').show(); } else { updateForecastMessage(1); } }) } else { $('#beachForecastTemplate').show(); } } var forecastMessages = [ 'Retrieving forecast, please wait...', 'Error retrieving forecast, please close this message and try again.' ]; function updateForecastMessage(messageNumber) { if (!isNaN(messageNumber)) { $('#beachForecastMessage') .html(forecastMessages[messageNumber]) .show(); } else { $('#beachForecastMessage').hide(); } } $('#beachForecastOverlay').click(function() { hideForecast(); }); function hideForecast() { $('#beachForecastOverlay').hide(); $('#beachForecastOverlayContent').hide(); $('#beachForecastTemplate').hide(); } function buildForecastHTML(json) { var numPeriods = 7; var bodyText = ""; bodyText += '
'+validName+' | '; } bodyText += '
'; } bodyText += ' |
'+weatherText+' | '; } bodyText += '
'+hilo+''+json.data.temperature[i]+'°F | '; } bodyText += '
'; for (var i = 0; i < 7; i++) { bodyText += ''+json.time.startPeriodName[i]+':'+json.data.text[i]; bodyText += '
'; } bodyText += '
Point Forecast: '+json.location.areaDescription+'
'+json.location.latitude+''+json.location.longitude+'(Elev. '+json.location.elevation+' ft)
'; bodyText += 'Visit your local NWS office at https://www.weather.gov/'+json.location.wfo.toLowerCase()+'
'; //bodyText += 'or call us at '+phoneNum; //bodyText += ''; //bodyText += srfText; //bodyText += ''; return (bodyText); } function associateEvents() { $('#beachForecastTemplateAction-Print').click(function() { var divToPrint=document.getElementById('beachForecastTemplate'); newWin= window.open(""); newWin.document.write(divToPrint.outerHTML); var newWinHead = newWin.document.getElementsByTagName("head")[0]; var $arrStyleSheets = $('style, link'); $arrStyleSheets.each(function() { newWinHead.appendChild(this.cloneNode(true)); }); newWin.document.body.style.backgroundImage = "none"; newWin.document.body.style.backgroundColor = "#fff"; newWin.print(); newWin.close(); }); } function getParameterByName(name) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^]*)"), results = regex.exec(location.search); return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); } })();
UNDER DEVELOPMENT
The map below is color-coded to indicate the forecast rip current risk level. Click on the beach area of your choice for more information, or click a beach umbrella for the detailed, beach forecast. View the product description document for more information on the rip current graphic. Comments are currently being accepted. |
|
Risk Level | Description | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Low | The risk of rip currents is low, however, life threatening rip currents may still occur especially near groins, jetties, reefs, and piers. Always swim near a lifeguard and remember to heed the advice of the local beach patrol and flag warning systems. | ||||||||||||||||||||||||||||||||||||
Moderate | Life threatening rip currents are possible. Always swim near a lifeguard and remember to heed the advice of the local beach patrol and flag warning systems. | ||||||||||||||||||||||||||||||||||||
High | Life threatening rip currents are likely. The surf zone is dangerous for all levels of swimmers. Stay out of the water. Remember to heed the advice of the local beach patrol and flag warning systems. | ||||||||||||||||||||||||||||||||||||
The Danger of Rip Currents Rip currents are powerful, channeled currents of water flowing away from shore. They typically extend from the shoreline, through the surf zone, and past the line of breaking waves. Rip currents can occur at any beach with breaking waves. En Espanol If you become caught in a rip current, yell for help and remain calm. Do not exhaust yourself and stay afloat while waiting for help. If you have to swim out of a rip current, swim parallel to shore and back toward the beach when possible. Do not attempt to swim directly against a rip current as you will tire quickly. Never assume the ocean is safe, even if the weather is nice. Hurricanes that are far away can still create deadly rip currents and waves. For maximum safety, swim near a lifeguard. Viewrip current safety videosat the National Weather Service YouTube channel. | |||||||||||||||||||||||||||||||||||||
Additional Resources
|