mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 01:57:39 -05:00
#2641 Enhanced radio reference integration. With a premium subscription and when you set your location on the map, the Spectral display includes radio reference layer that presents popup card in the spectrum that shows candidate systems or frequencies ordered by distance from your location. Background thread builds an offline cache of data to support this. Map plots this cache as tower pins on the map and you can hover and active site frequencies from the map. (#2642)
Updates sdrtrunk to use latest radio reference api jar. Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
187
src/main/java/io/github/dsheirer/map/RadioReferenceSiteCard.java
Normal file
187
src/main/java/io/github/dsheirer/map/RadioReferenceSiteCard.java
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import io.github.dsheirer.preference.radioreference.RadioReferencePreference;
|
||||
import io.github.dsheirer.rrapi.type.Coverage;
|
||||
import io.github.dsheirer.service.radioreference.DistanceFormat;
|
||||
import io.github.dsheirer.service.radioreference.SiteRecord;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HTML card with the details of radio reference trunked system sites, shown when the mouse is over a site's tower on
|
||||
* the map: the system and protocol, the site, and the site's channels.
|
||||
*/
|
||||
public final class RadioReferenceSiteCard
|
||||
{
|
||||
private static final int MAX_SITES = 3;
|
||||
private static final int CHANNEL_COLUMNS = 3;
|
||||
private static final int MAX_CHANNELS = 24;
|
||||
private static final String SEPARATOR = " · ";
|
||||
|
||||
private RadioReferenceSiteCard()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Card for the sites
|
||||
* @param sites under the mouse, nearest first
|
||||
* @param location of the user, for the distances, or null
|
||||
* @return HTML for a tooltip
|
||||
*/
|
||||
public static String html(List<SiteRecord> sites, RadioReferencePreference.Location location)
|
||||
{
|
||||
DecimalFormat frequencyFormat = new DecimalFormat("0.00000");
|
||||
StringBuilder sb = new StringBuilder("<html><body style='padding:3px'>");
|
||||
int shown = Math.min(sites.size(), MAX_SITES);
|
||||
|
||||
for(int index = 0; index < shown; index++)
|
||||
{
|
||||
SiteRecord site = sites.get(index);
|
||||
|
||||
if(index > 0)
|
||||
{
|
||||
sb.append("<hr>");
|
||||
}
|
||||
|
||||
sb.append("<div><b>").append(escape(site.systemName() != null ? site.systemName() : "(unnamed system)"))
|
||||
.append("</b></div>");
|
||||
appendLine(sb, site.protocol(), site.voice());
|
||||
|
||||
String siteName = "Site " + site.siteNumber();
|
||||
String distance = location != null ? DistanceFormat.format(Coverage.distanceKilometers(
|
||||
location.latitude(), location.longitude(), site.latitude(), site.longitude())) : null;
|
||||
appendLine(sb, siteName, site.description(), site.county(), distance);
|
||||
appendLine(sb, site.nac() != null ? "NAC " + site.nac() : null,
|
||||
site.rfss() > 0 ? "RFSS " + site.rfss() : null, site.zone() > 0 ? "Zone " + site.zone() : null);
|
||||
appendChannels(sb, site.channels(), frequencyFormat);
|
||||
}
|
||||
|
||||
if(sites.size() > shown)
|
||||
{
|
||||
sb.append("<hr><div>+ ").append(sites.size() - shown).append(" more site")
|
||||
.append(sites.size() - shown > 1 ? "s" : "").append(" here</div>");
|
||||
}
|
||||
|
||||
sb.append("</body></html>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Channels in columns, control channels marked C and alternate control channels marked A
|
||||
*/
|
||||
private static void appendChannels(StringBuilder sb, List<SiteRecord.SiteChannel> channels,
|
||||
DecimalFormat frequencyFormat)
|
||||
{
|
||||
if(channels == null || channels.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int shown = Math.min(channels.size(), MAX_CHANNELS);
|
||||
boolean control = false;
|
||||
sb.append("<table cellspacing='0' cellpadding='1'>");
|
||||
|
||||
for(int index = 0; index < shown; index++)
|
||||
{
|
||||
if(index % CHANNEL_COLUMNS == 0)
|
||||
{
|
||||
sb.append("<tr>");
|
||||
}
|
||||
|
||||
SiteRecord.SiteChannel channel = channels.get(index);
|
||||
sb.append("<td style='padding-right:8px'>").append(frequencyFormat.format(channel.frequencyHz() / 1E6));
|
||||
|
||||
if("Control".equals(channel.use()))
|
||||
{
|
||||
sb.append(" <b>C</b>");
|
||||
control = true;
|
||||
}
|
||||
else if("Alternate control".equals(channel.use()))
|
||||
{
|
||||
sb.append(" <b>A</b>");
|
||||
control = true;
|
||||
}
|
||||
|
||||
sb.append("</td>");
|
||||
|
||||
if(index % CHANNEL_COLUMNS == CHANNEL_COLUMNS - 1)
|
||||
{
|
||||
sb.append("</tr>");
|
||||
}
|
||||
}
|
||||
|
||||
if(shown % CHANNEL_COLUMNS != 0)
|
||||
{
|
||||
sb.append("</tr>");
|
||||
}
|
||||
|
||||
sb.append("</table>");
|
||||
List<String> notes = new ArrayList<>();
|
||||
|
||||
if(channels.size() > shown)
|
||||
{
|
||||
notes.add("+ " + (channels.size() - shown) + " more channels");
|
||||
}
|
||||
|
||||
if(control)
|
||||
{
|
||||
notes.add("C control, A alternate control");
|
||||
}
|
||||
|
||||
if(!notes.isEmpty())
|
||||
{
|
||||
appendLine(sb, notes.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a line with the non-empty values separated by dots, or nothing when all are empty
|
||||
*/
|
||||
private static void appendLine(StringBuilder sb, String... values)
|
||||
{
|
||||
StringBuilder line = new StringBuilder();
|
||||
|
||||
for(String value: values)
|
||||
{
|
||||
if(value != null && !value.isBlank())
|
||||
{
|
||||
if(!line.isEmpty())
|
||||
{
|
||||
line.append(SEPARATOR);
|
||||
}
|
||||
|
||||
line.append(escape(value));
|
||||
}
|
||||
}
|
||||
|
||||
if(!line.isEmpty())
|
||||
{
|
||||
sb.append("<div>").append(line).append("</div>");
|
||||
}
|
||||
}
|
||||
|
||||
private static String escape(String text)
|
||||
{
|
||||
return text.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user