mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 01:57:39 -05:00
Updates sdrtrunk to use latest radio reference api jar. Co-authored-by: denny <dennis.sheirer@gmail.com>
188 lines
6.1 KiB
Java
188 lines
6.1 KiB
Java
/*
|
|
* *****************************************************************************
|
|
* 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("\"", """);
|
|
}
|
|
}
|