feat: added Worked All Italian Provinces award and FFMA
This commit is contained in:
+16
-9
@@ -1180,22 +1180,29 @@ export default function App() {
|
||||
// Effective antenna heading(s): the rotor azimuth, transformed by the
|
||||
// Ultrabeam pattern when one is active — reversed (180°) points opposite,
|
||||
// bidirectional radiates both ways, normal is the heading itself.
|
||||
// Headings are rounded to whole degrees: a rotor reports a jittering float, and
|
||||
// a fraction of a degree changes nothing on a compass or a beam lobe — but it
|
||||
// does invalidate every memo downstream and force the map to rebuild its whole
|
||||
// overlay on each reading.
|
||||
const rotorAz = useMemo<number | null>(() => (
|
||||
rotatorHeading.enabled && rotatorHeading.ok
|
||||
? Math.round((((rotatorHeading.azimuth % 360) + 360) % 360)) % 360
|
||||
: null
|
||||
), [rotatorHeading.enabled, rotatorHeading.ok, rotatorHeading.azimuth]);
|
||||
|
||||
const beamHeadings = useMemo<number[]>(() => {
|
||||
if (!(rotatorHeading.enabled && rotatorHeading.ok)) return [];
|
||||
const base = ((rotatorHeading.azimuth % 360) + 360) % 360;
|
||||
if (rotorAz == null) return [];
|
||||
if (ubStatus.enabled && ubStatus.connected) {
|
||||
if (ubStatus.direction === 1) return [(base + 180) % 360];
|
||||
if (ubStatus.direction === 2) return [base, (base + 180) % 360];
|
||||
if (ubStatus.direction === 1) return [(rotorAz + 180) % 360];
|
||||
if (ubStatus.direction === 2) return [rotorAz, (rotorAz + 180) % 360];
|
||||
}
|
||||
return [base];
|
||||
}, [rotatorHeading.enabled, rotatorHeading.ok, rotatorHeading.azimuth, ubStatus.enabled, ubStatus.connected, ubStatus.direction]);
|
||||
return [rotorAz];
|
||||
}, [rotorAz, ubStatus.enabled, ubStatus.connected, ubStatus.direction]);
|
||||
|
||||
// Mechanical boom (rotor) heading + Ultrabeam pattern — so the compass/map can
|
||||
// show where the antenna physically points (boom) vs where it radiates when
|
||||
// the Ultrabeam is reversed/bidirectional.
|
||||
const boomHeading = useMemo<number | null>(() => (
|
||||
rotatorHeading.enabled && rotatorHeading.ok ? ((rotatorHeading.azimuth % 360) + 360) % 360 : null
|
||||
), [rotatorHeading.enabled, rotatorHeading.ok, rotatorHeading.azimuth]);
|
||||
const boomHeading = rotorAz;
|
||||
const ubPattern = useMemo<'normal' | 'reverse' | 'bi' | null>(() => {
|
||||
if (!(ubStatus.enabled && ubStatus.connected)) return null;
|
||||
return ubStatus.direction === 1 ? 'reverse' : ubStatus.direction === 2 ? 'bi' : 'normal';
|
||||
|
||||
@@ -742,10 +742,11 @@ function ReferencesPanel({ code, presets, meta, onUpdateOnline, updating, onChan
|
||||
<button className="text-muted-foreground hover:text-destructive" onClick={() => delRef(sel.code)}><Trash2 className="size-4" /></button>
|
||||
</div>
|
||||
<Field2 label={t('awed.description')}><Input className="h-8" value={sel.name ?? ''} onChange={(e) => patchSel({ name: e.target.value })} /></Field2>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{/* One per row: side by side, each half spends 120px of its width on
|
||||
the label column and the input is left too narrow to read a group
|
||||
name ("Basilicata" → "Basili"). */}
|
||||
<Field2 label={t('awed.group')}><Input className="h-8" value={sel.group ?? ''} onChange={(e) => patchSel({ group: e.target.value })} /></Field2>
|
||||
<Field2 label={t('awed.subgroup')}><Input className="h-8" value={sel.subgrp ?? ''} onChange={(e) => patchSel({ subgrp: e.target.value })} /></Field2>
|
||||
</div>
|
||||
<Field2 label="DXCC"><Input type="number" className="h-8 w-32 font-mono" value={sel.dxcc || ''} onChange={(e) => patchSel({ dxcc: parseInt(e.target.value, 10) || 0 })} /></Field2>
|
||||
<Field2 label={t('awed.patternRegex')}><Input className="h-8 font-mono text-xs" value={sel.pattern ?? ''} onChange={(e) => patchSel({ pattern: e.target.value })} placeholder={t('awed.perRefRegex')} /></Field2>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
|
||||
@@ -64,6 +64,24 @@ function loadBasemap(): BasemapKey {
|
||||
return v === 'voyager' || v === 'street' || v === 'satellite' ? v : 'light';
|
||||
}
|
||||
|
||||
// addBasemap (re)installs the imagery layer and, for satellite, its transparent
|
||||
// place-name overlay. updateWhenIdle/keepBuffer keep the number of live tiles
|
||||
// down: satellite loads TWO tile layers, so its tile count — and the composited
|
||||
// layers WebView2 has to hold — is double every other basemap's.
|
||||
function addBasemap(
|
||||
m: L.Map,
|
||||
key: BasemapKey,
|
||||
base: React.MutableRefObject<L.TileLayer | null>,
|
||||
labels: React.MutableRefObject<L.TileLayer | null>,
|
||||
) {
|
||||
if (base.current) { m.removeLayer(base.current); base.current = null; }
|
||||
if (labels.current) { m.removeLayer(labels.current); labels.current = null; }
|
||||
const bm = BASEMAPS[key];
|
||||
const opts: L.TileLayerOptions = { maxZoom: 19, updateWhenIdle: true, updateWhenZooming: false, keepBuffer: 1 };
|
||||
base.current = L.tileLayer(bm.url, { ...opts, attribution: bm.attr, subdomains: bm.subdomains ?? 'abc' }).addTo(m);
|
||||
if (bm.labelsUrl) labels.current = L.tileLayer(bm.labelsUrl, opts).addTo(m);
|
||||
}
|
||||
|
||||
function dot(color: string): L.DivIcon {
|
||||
return L.divIcon({
|
||||
className: '',
|
||||
@@ -103,11 +121,14 @@ export function WorldMap({ fromGrid, toGrid, fromLabel, toLabel, beamAzimuths, b
|
||||
// One-time map creation.
|
||||
useEffect(() => {
|
||||
if (worldRef.current && !worldMap.current) {
|
||||
const m = L.map(worldRef.current, { zoomControl: true, attributionControl: true, worldCopyJump: true })
|
||||
// preferCanvas: the beam lobe is a dense FAN of translucent radials — up to
|
||||
// ~120 thick strokes with a bidirectional Ultrabeam. As SVG that is ~120
|
||||
// composited paths re-rasterised on every pan, zoom and redraw, which is
|
||||
// enough to blow WebView2's raster budget and leave the window painting in
|
||||
// patches. On canvas it is a single layer.
|
||||
const m = L.map(worldRef.current, { zoomControl: true, attributionControl: true, worldCopyJump: true, preferCanvas: true })
|
||||
.setView([20, 0], 1);
|
||||
const bm = BASEMAPS[basemap];
|
||||
baseLayer.current = L.tileLayer(bm.url, { attribution: bm.attr, subdomains: bm.subdomains ?? 'abc', maxZoom: 19 }).addTo(m);
|
||||
if (bm.labelsUrl) labelsLayer.current = L.tileLayer(bm.labelsUrl, { maxZoom: 19 }).addTo(m);
|
||||
addBasemap(m, basemap, baseLayer, labelsLayer);
|
||||
worldOverlay.current = L.layerGroup().addTo(m);
|
||||
worldMap.current = m;
|
||||
const sv = loadMapView();
|
||||
@@ -115,7 +136,12 @@ export function WorldMap({ fromGrid, toGrid, fromLabel, toLabel, beamAzimuths, b
|
||||
m.on('moveend', () => { if (!autoZoomRef.current) saveMapView(m); });
|
||||
}
|
||||
const t = window.setTimeout(() => { worldMap.current?.invalidateSize(); }, 80);
|
||||
return () => window.clearTimeout(t);
|
||||
// Resizing the pane is the ONLY thing that needs invalidateSize. Calling it on
|
||||
// every overlay redraw (as before) forced a full repaint of the map each time
|
||||
// the rotor moved a degree.
|
||||
const ro = new ResizeObserver(() => worldMap.current?.invalidateSize());
|
||||
if (worldRef.current) ro.observe(worldRef.current);
|
||||
return () => { window.clearTimeout(t); ro.disconnect(); };
|
||||
}, []);
|
||||
|
||||
// Swap the basemap (and its optional place-name overlay) when the operator
|
||||
@@ -123,12 +149,7 @@ export function WorldMap({ fromGrid, toGrid, fromLabel, toLabel, beamAzimuths, b
|
||||
// overlayPane, always above any tile layer, so nothing to re-stack there.
|
||||
useEffect(() => {
|
||||
const m = worldMap.current;
|
||||
if (!m) return;
|
||||
if (baseLayer.current) { m.removeLayer(baseLayer.current); baseLayer.current = null; }
|
||||
if (labelsLayer.current) { m.removeLayer(labelsLayer.current); labelsLayer.current = null; }
|
||||
const bm = BASEMAPS[basemap];
|
||||
baseLayer.current = L.tileLayer(bm.url, { attribution: bm.attr, subdomains: bm.subdomains ?? 'abc', maxZoom: 19 }).addTo(m);
|
||||
if (bm.labelsUrl) labelsLayer.current = L.tileLayer(bm.labelsUrl, { maxZoom: 19 }).addTo(m);
|
||||
if (m) addBasemap(m, basemap, baseLayer, labelsLayer);
|
||||
}, [basemap]);
|
||||
|
||||
// Redraw overlays whenever the operator/DX grids (or beam) change.
|
||||
@@ -224,9 +245,14 @@ export function WorldMap({ fromGrid, toGrid, fromLabel, toLabel, beamAzimuths, b
|
||||
wm.setView([from.lat, from.lon], 3);
|
||||
}
|
||||
}
|
||||
setTimeout(() => { wm.invalidateSize(); }, 0);
|
||||
// No invalidateSize() here — a ResizeObserver handles the only case that needs
|
||||
// it. Forcing a full map repaint on every redraw is what made a moving rotor
|
||||
// thrash the compositor.
|
||||
// Headings are rounded in the deps: a rotor reports a jittering float, and a
|
||||
// tenth of a degree is invisible on a 5500 km lobe but rebuilds every polyline.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fromGrid, toGrid, fromLabel, toLabel, (beamAzimuths ?? []).map((a) => Math.round(a)).join(','), beamWidth, boomAzimuth, autoZoom, zoomSignal]);
|
||||
}, [fromGrid, toGrid, fromLabel, toLabel, (beamAzimuths ?? []).map((a) => Math.round(a)).join(','), beamWidth,
|
||||
boomAzimuth == null ? null : Math.round(boomAzimuth), autoZoom, zoomSignal]);
|
||||
|
||||
const path = pathBetween(fromGrid, toGrid);
|
||||
|
||||
|
||||
+28
-12
@@ -693,7 +693,15 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
||||
// only while nothing has matched yet, and stop at the first that yields a
|
||||
// reference (short-circuit). So a province already found by NAME isn't also
|
||||
// re-derived, possibly differently, from a later city-regex rule.
|
||||
found := searchOne(d.Field, d.MatchBy, re, d.ExactMatch, d.LeadingStr, d.TrailingStr, "", q, rl, predefined)
|
||||
//
|
||||
// The short-circuit tests what a rule REALLY yielded — the references that
|
||||
// survive the predefined list — not its raw candidates. A rule can always
|
||||
// produce a raw candidate and still find nothing: "the whole ADDRESS field is
|
||||
// the code" hands back "SERIATE (BG) 24068 ITALY", which is not a province.
|
||||
// Testing the raw candidate would call that a hit, skip every fallback, and
|
||||
// only then drop it as unlisted — leaving the QSO unmatched even though the
|
||||
// next rule ("find the code inside the QTH") would have found BG.
|
||||
found := keepRefs(predefined, rl, searchOne(d.Field, d.MatchBy, re, d.ExactMatch, d.LeadingStr, d.TrailingStr, "", q, rl, predefined))
|
||||
for i := 0; len(found) == 0 && i < len(d.OrRules); i++ {
|
||||
r := &d.OrRules[i]
|
||||
var rre *regexp.Regexp
|
||||
@@ -704,7 +712,7 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
||||
}
|
||||
rre = c
|
||||
}
|
||||
found = searchOne(r.Field, r.MatchBy, rre, r.ExactMatch, r.LeadingStr, r.TrailingStr, r.Prefix, q, rl, predefined)
|
||||
found = keepRefs(predefined, rl, searchOne(r.Field, r.MatchBy, rre, r.ExactMatch, r.LeadingStr, r.TrailingStr, r.Prefix, q, rl, predefined))
|
||||
}
|
||||
|
||||
// Merge operator-assigned references (manual override, ManualRefsKey). Lets
|
||||
@@ -714,18 +722,26 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
||||
// hand. Applied HERE (not just in MatchQSO) so Compute — which powers the
|
||||
// awards panel and the per-QSO refs editor — honours overrides too. For a
|
||||
// predefined award the ref is still validated against the list below.
|
||||
for _, c := range manualRefs(q, d.Code) {
|
||||
found = append(found, normalizeRef(c))
|
||||
}
|
||||
|
||||
if !predefined {
|
||||
found = append(found, keepRefs(predefined, rl, manualRefs(q, d.Code))...)
|
||||
return dedupe(found)
|
||||
}
|
||||
|
||||
// keepRefs reduces a rule's raw candidates to the references that actually count.
|
||||
// For a predefined award that means the codes on the list, enabled. The
|
||||
// award-level DXCCFilter already scopes which QSOs are considered (see inScope),
|
||||
// so we do NOT additionally require the QSO's entity to match the reference's own
|
||||
// DXCC — that wrongly excluded e.g. WAS Alaska (state AK is DXCC entity 6, not
|
||||
// 291). Per-reference DXCC stays metadata for the picker.
|
||||
func keepRefs(predefined bool, rl refList, found []string) []string {
|
||||
if !predefined {
|
||||
out := make([]string, 0, len(found))
|
||||
for _, c := range found {
|
||||
if c = normalizeRef(c); c != "" {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return dedupe(out)
|
||||
}
|
||||
// Enforce the predefined list: keep only listed, valid references. The
|
||||
// award-level DXCCFilter already scopes which QSOs are considered (see
|
||||
// inScope), so we do NOT additionally require the QSO's entity to match the
|
||||
// reference's own DXCC — that wrongly excluded e.g. WAS Alaska (state AK is
|
||||
// DXCC entity 6, not 291). Per-reference DXCC stays metadata for the picker.
|
||||
var out []string
|
||||
seen := map[string]struct{}{}
|
||||
for _, c := range found {
|
||||
|
||||
@@ -354,6 +354,36 @@ func TestCatalogFFMA(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// WAIP: the primary rule takes the WHOLE address as the province code (exact
|
||||
// match), which can never be one — but it does produce a raw candidate. The OR
|
||||
// fallback (find the code inside the QTH, "SERIATE (BG)") is the rule that works,
|
||||
// and it must still run: a rule that yields nothing VALID is not a hit.
|
||||
func TestComputeOrFallbackAfterUnlistedPrimary(t *testing.T) {
|
||||
def := Def{
|
||||
Code: "WAIP", Name: "Worked All Italian Provinces", Valid: true,
|
||||
Type: TypeReference, Field: "address", MatchBy: "code", ExactMatch: true,
|
||||
OrRules: []OrRule{{Field: "qth", MatchBy: "code"}}, // not exact → search inside the field
|
||||
Confirm: []string{"lotw", "qsl"},
|
||||
}
|
||||
metas := []RefMeta{
|
||||
{Code: "BG", Name: "Bergamo", Valid: true},
|
||||
{Code: "MI", Name: "Milano", Valid: true},
|
||||
}
|
||||
qsos := []qso.QSO{
|
||||
{Callsign: "I2IFT", Band: "30m", QTH: "SERIATE (BG)", Address: "Seriate (Bg) 24068 Italy", LOTWRcvd: "Y"},
|
||||
}
|
||||
r := Compute([]Def{def}, qsos, map[string][]RefMeta{"WAIP": metas}, nil)[0]
|
||||
if r.Worked != 1 {
|
||||
t.Fatalf("WAIP worked = %d, want 1 (BG, from the QTH fallback)", r.Worked)
|
||||
}
|
||||
for _, rf := range r.Refs {
|
||||
if rf.Ref == "BG" && rf.Worked {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("BG not worked; refs = %v", refCodes(r))
|
||||
}
|
||||
|
||||
func refCodes(r Result) []string {
|
||||
out := make([]string, 0, len(r.Refs))
|
||||
for _, rf := range r.Refs {
|
||||
|
||||
@@ -0,0 +1,929 @@
|
||||
{
|
||||
"version": 1,
|
||||
"exported_at": "2026-07-13T21:57:23Z",
|
||||
"awards": [
|
||||
{
|
||||
"def": {
|
||||
"code": "WAIP",
|
||||
"name": "ARI Worked All Italian Provinces",
|
||||
"valid": true,
|
||||
"url": "https://ari.it/en/english-area/awards/1734-waip-worked-all-italian-provinces.html",
|
||||
"ref_url": "https://ari.it/en/english-area/awards/1734-waip-worked-all-italian-provinces.html",
|
||||
"type": "REFERENCE",
|
||||
"field": "qth",
|
||||
"match_by": "code",
|
||||
"exact_match": true,
|
||||
"pattern": "",
|
||||
"or_rules": [
|
||||
{
|
||||
"field": "qth",
|
||||
"match_by": "description"
|
||||
},
|
||||
{
|
||||
"field": "address",
|
||||
"match_by": "code"
|
||||
},
|
||||
{
|
||||
"field": "address",
|
||||
"match_by": "description"
|
||||
}
|
||||
],
|
||||
"dxcc_filter": [
|
||||
248
|
||||
],
|
||||
"confirm": [
|
||||
"lotw",
|
||||
"qsl"
|
||||
],
|
||||
"validate": [
|
||||
"lotw",
|
||||
"qsl"
|
||||
],
|
||||
"total": 0,
|
||||
"builtin": false
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"code": "AG",
|
||||
"name": "Agrigento",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AL",
|
||||
"name": "Alessandria",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AN",
|
||||
"name": "Ancona",
|
||||
"dxcc": 248,
|
||||
"group": "Marche",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AO",
|
||||
"name": "Aosta",
|
||||
"dxcc": 248,
|
||||
"group": "Val d'Aosta",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AP",
|
||||
"name": "Ascoli Piceno",
|
||||
"dxcc": 248,
|
||||
"group": "Marche",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AQ",
|
||||
"name": "L'Aquila",
|
||||
"dxcc": 248,
|
||||
"group": "Abruzzo",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AR",
|
||||
"name": "Arezzo",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AT",
|
||||
"name": "Asti",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "AV",
|
||||
"name": "Avellino",
|
||||
"dxcc": 248,
|
||||
"group": "Campania",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BA",
|
||||
"name": "Bari",
|
||||
"dxcc": 248,
|
||||
"group": "Puglia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BG",
|
||||
"name": "Bergamo",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BI",
|
||||
"name": "Biella",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BL",
|
||||
"name": "Belluno",
|
||||
"dxcc": 248,
|
||||
"group": "Veneto",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BN",
|
||||
"name": "Benevento",
|
||||
"dxcc": 248,
|
||||
"group": "Campania",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BO",
|
||||
"name": "Bologna",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BR",
|
||||
"name": "Brindisi",
|
||||
"dxcc": 248,
|
||||
"group": "Puglia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BS",
|
||||
"name": "Brescia",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BT",
|
||||
"name": "Barletta-Andria-Trani",
|
||||
"dxcc": 248,
|
||||
"group": "Puglia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "BZ",
|
||||
"name": "Bolzano/Bozen",
|
||||
"dxcc": 248,
|
||||
"group": "Trentino-AltoAdige/Südtirol",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CA",
|
||||
"name": "Cagliari",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CB",
|
||||
"name": "Campobasso",
|
||||
"dxcc": 248,
|
||||
"group": "Molise",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CE",
|
||||
"name": "Caserta",
|
||||
"dxcc": 248,
|
||||
"group": "Campania",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CH",
|
||||
"name": "Chieti",
|
||||
"dxcc": 248,
|
||||
"group": "Abruzzo",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CI",
|
||||
"name": "Carbonia-Iglesias",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CL",
|
||||
"name": "Caltanissetta",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CN",
|
||||
"name": "Cuneo",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CO",
|
||||
"name": "Como",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CR",
|
||||
"name": "Cremona",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CS",
|
||||
"name": "Cosenza",
|
||||
"dxcc": 248,
|
||||
"group": "Calabria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CT",
|
||||
"name": "Catania",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "CZ",
|
||||
"name": "Catanzaro",
|
||||
"dxcc": 248,
|
||||
"group": "Calabria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "EN",
|
||||
"name": "Enna",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "FC",
|
||||
"name": "Forlì-Cesena",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "FE",
|
||||
"name": "Ferrara",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "FG",
|
||||
"name": "Foggia",
|
||||
"dxcc": 248,
|
||||
"group": "Puglia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "FI",
|
||||
"name": "Firenze",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "FM",
|
||||
"name": "Fermo",
|
||||
"dxcc": 248,
|
||||
"group": "Marche",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "FR",
|
||||
"name": "Frosinone",
|
||||
"dxcc": 248,
|
||||
"group": "Lazio",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "GE",
|
||||
"name": "Genova",
|
||||
"dxcc": 248,
|
||||
"group": "Liguria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "GO",
|
||||
"name": "Gorizia",
|
||||
"dxcc": 248,
|
||||
"group": "Friuli-Venezia Giulia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "GR",
|
||||
"name": "Grosseto",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "IM",
|
||||
"name": "Imperia",
|
||||
"dxcc": 248,
|
||||
"group": "Liguria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "IS",
|
||||
"name": "Isernia",
|
||||
"dxcc": 248,
|
||||
"group": "Molise",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "KR",
|
||||
"name": "Crotone",
|
||||
"dxcc": 248,
|
||||
"group": "Calabria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "LC",
|
||||
"name": "Lecco",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "LE",
|
||||
"name": "Lecce",
|
||||
"dxcc": 248,
|
||||
"group": "Puglia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "LI",
|
||||
"name": "Livorno",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "LO",
|
||||
"name": "Lodi",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "LT",
|
||||
"name": "Latina",
|
||||
"dxcc": 248,
|
||||
"group": "Lazio",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "LU",
|
||||
"name": "Lucca",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "MB",
|
||||
"name": "Monza e Brianza",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "MC",
|
||||
"name": "Macerata",
|
||||
"dxcc": 248,
|
||||
"group": "Marche",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "ME",
|
||||
"name": "Messina",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "MI",
|
||||
"name": "Milano",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "MN",
|
||||
"name": "Mantova",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "MO",
|
||||
"name": "Modena",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "MS",
|
||||
"name": "Massa-Carrara",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "MT",
|
||||
"name": "Matera",
|
||||
"dxcc": 248,
|
||||
"group": "Basilicata",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "NA",
|
||||
"name": "Napoli",
|
||||
"dxcc": 248,
|
||||
"group": "Campania",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "NO",
|
||||
"name": "Novara",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "NU",
|
||||
"name": "Nuoro",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "OG",
|
||||
"name": "Ogliastra",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "OR",
|
||||
"name": "Oristano",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "OT",
|
||||
"name": "Olbia-Tempio",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PA",
|
||||
"name": "Palermo",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PC",
|
||||
"name": "Piacenza",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PD",
|
||||
"name": "Padova",
|
||||
"dxcc": 248,
|
||||
"group": "Veneto",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PE",
|
||||
"name": "Pescara",
|
||||
"dxcc": 248,
|
||||
"group": "Abruzzo",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PG",
|
||||
"name": "Perugia",
|
||||
"dxcc": 248,
|
||||
"group": "Umbria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PI",
|
||||
"name": "Pisa",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PN",
|
||||
"name": "Pordenone",
|
||||
"dxcc": 248,
|
||||
"group": "Friuli-Venezia Giulia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PO",
|
||||
"name": "Prato",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PR",
|
||||
"name": "Parma",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PT",
|
||||
"name": "Pistoia",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PU",
|
||||
"name": "Pesaro-Urbino",
|
||||
"dxcc": 248,
|
||||
"group": "Marche",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PV",
|
||||
"name": "Pavia",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "PZ",
|
||||
"name": "Potenza",
|
||||
"dxcc": 248,
|
||||
"group": "Basilicata",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RA",
|
||||
"name": "Ravenna",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RC",
|
||||
"name": "Reggio Calabria",
|
||||
"dxcc": 248,
|
||||
"group": "Calabria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RE",
|
||||
"name": "Reggio Emilia",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RG",
|
||||
"name": "Ragusa",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RI",
|
||||
"name": "Rieti",
|
||||
"dxcc": 248,
|
||||
"group": "Lazio",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RM",
|
||||
"name": "Roma",
|
||||
"dxcc": 248,
|
||||
"group": "Lazio",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RN",
|
||||
"name": "Rimini",
|
||||
"dxcc": 248,
|
||||
"group": "Emilia-Romagna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "RO",
|
||||
"name": "Rovigo",
|
||||
"dxcc": 248,
|
||||
"group": "Veneto",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "SA",
|
||||
"name": "Salerno",
|
||||
"dxcc": 248,
|
||||
"group": "Campania",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "SI",
|
||||
"name": "Siena",
|
||||
"dxcc": 248,
|
||||
"group": "Toscana",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "SO",
|
||||
"name": "Sondrio",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "SP",
|
||||
"name": "La Spezia",
|
||||
"dxcc": 248,
|
||||
"group": "Liguria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "SR",
|
||||
"name": "Siracusa",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "SS",
|
||||
"name": "Sassari",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "SV",
|
||||
"name": "Savona",
|
||||
"dxcc": 248,
|
||||
"group": "Liguria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TA",
|
||||
"name": "Taranto",
|
||||
"dxcc": 248,
|
||||
"group": "Puglia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TE",
|
||||
"name": "Teramo",
|
||||
"dxcc": 248,
|
||||
"group": "Abruzzo",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TN",
|
||||
"name": "Trento",
|
||||
"dxcc": 248,
|
||||
"group": "Trentino-AltoAdige/Südtirol",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TO",
|
||||
"name": "Torino",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TP",
|
||||
"name": "Trapani",
|
||||
"dxcc": 248,
|
||||
"group": "Sicilia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TR",
|
||||
"name": "Terni",
|
||||
"dxcc": 248,
|
||||
"group": "Umbria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TS",
|
||||
"name": "Trieste",
|
||||
"dxcc": 248,
|
||||
"group": "Friuli-Venezia Giulia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "TV",
|
||||
"name": "Treviso",
|
||||
"dxcc": 248,
|
||||
"group": "Veneto",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "UD",
|
||||
"name": "Udine",
|
||||
"dxcc": 248,
|
||||
"group": "Friuli-Venezia Giulia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VA",
|
||||
"name": "Varese",
|
||||
"dxcc": 248,
|
||||
"group": "Lombardia",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VB",
|
||||
"name": "Verbano-Cusio-Ossola",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VC",
|
||||
"name": "Vercelli",
|
||||
"dxcc": 248,
|
||||
"group": "Piemonte",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VE",
|
||||
"name": "Venezia",
|
||||
"dxcc": 248,
|
||||
"group": "Veneto",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VI",
|
||||
"name": "Vicenza",
|
||||
"dxcc": 248,
|
||||
"group": "Veneto",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VR",
|
||||
"name": "Verona",
|
||||
"dxcc": 248,
|
||||
"group": "Veneto",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VS",
|
||||
"name": "Medio Campidano",
|
||||
"dxcc": 248,
|
||||
"group": "Sardegna",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VT",
|
||||
"name": "Viterbo",
|
||||
"dxcc": 248,
|
||||
"group": "Lazio",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"code": "VV",
|
||||
"name": "Vibo Valentia",
|
||||
"dxcc": 248,
|
||||
"group": "Calabria",
|
||||
"subgrp": "",
|
||||
"valid": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user