@@ -43,6 +43,11 @@ app.use(cors({ origin: '*' }));
4343// DXCluster connection and spot cache
4444let spots = [ ] ;
4545
46+ // Indexes for faster lookups
47+ const bandIndex = new Map ( ) ; // Map<band, Set<spot>>
48+ const frequencyIndex = new Map ( ) ; // Map<frequency, spot>
49+ const sourceIndex = new Map ( ) ; // Map<source, Set<spot>>
50+
4651
4752// -----------------------------------
4853// Utility Functions
@@ -267,23 +272,103 @@ async function handlespot(spot, spot_source = "cluster"){
267272
268273 //lookup band
269274 dxSpot . band = qrg2band ( dxSpot . frequency * 1000 ) ;
270-
275+
271276 //push spot to cache
272277 spots . push ( dxSpot ) ;
273-
278+
279+ // Update indexes
280+ updateIndexes ( dxSpot ) ;
281+
274282 //empty out spots if maximum retainment is reached
275283 if ( spots . length > config . maxcache ) {
276- spots . shift ( ) ;
284+ const removed = spots . shift ( ) ;
285+ removeFromIndexes ( removed ) ;
277286 }
278287
279288 //reduce spots
289+ const oldLength = spots . length ;
280290 spots = reduce_spots ( spots ) ;
291+
292+ // If spots were reduced, rebuild indexes
293+ if ( spots . length !== oldLength ) {
294+ rebuildIndexes ( ) ;
295+ }
281296
282297 } catch ( e ) {
283298 console . error ( "Error processing spot:" , e ) ;
284299 }
285300}
286301
302+ // -----------------------------------
303+ // Index Management Functions
304+ // -----------------------------------
305+
306+ /**
307+ * Updates all indexes when a new spot is added
308+ */
309+ function updateIndexes ( spot ) {
310+ // Update band index
311+ if ( spot . band ) {
312+ if ( ! bandIndex . has ( spot . band ) ) {
313+ bandIndex . set ( spot . band , new Set ( ) ) ;
314+ }
315+ bandIndex . get ( spot . band ) . add ( spot ) ;
316+ }
317+
318+ // Update frequency index (keep only latest spot per frequency)
319+ const existing = frequencyIndex . get ( spot . frequency ) ;
320+ if ( ! existing || Date . parse ( spot . when ) > Date . parse ( existing . when ) ) {
321+ frequencyIndex . set ( spot . frequency , spot ) ;
322+ }
323+
324+ // Update source index
325+ if ( spot . source ) {
326+ if ( ! sourceIndex . has ( spot . source ) ) {
327+ sourceIndex . set ( spot . source , new Set ( ) ) ;
328+ }
329+ sourceIndex . get ( spot . source ) . add ( spot ) ;
330+ }
331+ }
332+
333+ /**
334+ * Removes a spot from all indexes
335+ */
336+ function removeFromIndexes ( spot ) {
337+ if ( ! spot ) return ;
338+
339+ // Remove from band index
340+ if ( spot . band && bandIndex . has ( spot . band ) ) {
341+ bandIndex . get ( spot . band ) . delete ( spot ) ;
342+ if ( bandIndex . get ( spot . band ) . size === 0 ) {
343+ bandIndex . delete ( spot . band ) ;
344+ }
345+ }
346+
347+ // Remove from frequency index if this is the current spot
348+ if ( frequencyIndex . get ( spot . frequency ) === spot ) {
349+ frequencyIndex . delete ( spot . frequency ) ;
350+ }
351+
352+ // Remove from source index
353+ if ( spot . source && sourceIndex . has ( spot . source ) ) {
354+ sourceIndex . get ( spot . source ) . delete ( spot ) ;
355+ if ( sourceIndex . get ( spot . source ) . size === 0 ) {
356+ sourceIndex . delete ( spot . source ) ;
357+ }
358+ }
359+ }
360+
361+ /**
362+ * Rebuilds all indexes from scratch
363+ */
364+ function rebuildIndexes ( ) {
365+ bandIndex . clear ( ) ;
366+ frequencyIndex . clear ( ) ;
367+ sourceIndex . clear ( ) ;
368+
369+ spots . forEach ( spot => updateIndexes ( spot ) ) ;
370+ }
371+
287372function get_singlespot ( qrg ) {
288373 let ret = { } ;
289374 let youngest = Date . parse ( '1970-01-01T00:00:00.000Z' ) ;
@@ -304,7 +389,22 @@ let consecutiveErrorCount = 0;
304389const dxccServer = config . dxcc_lookup_wavelog_url ; // The WaveLog server
305390let abortController = null ; // For aborting ongoing requests
306391
392+ // DXCC cache: Map<callsign, {data, timestamp}>
393+ const dxccCache = new Map ( ) ;
394+ const DXCC_CACHE_TTL = 24 * 60 * 60 * 1000 ; // 24 hours in milliseconds
395+
307396async function dxcc_lookup ( call ) {
397+ // Check cache first
398+ const cached = dxccCache . get ( call ) ;
399+ if ( cached ) {
400+ const age = Date . now ( ) - cached . timestamp ;
401+ if ( age < DXCC_CACHE_TTL ) {
402+ return cached . data ;
403+ } else {
404+ // Expired, remove from cache
405+ dxccCache . delete ( call ) ;
406+ }
407+ }
308408 let timeoutId = null ; // Initialize timeoutId to null
309409
310410 try {
@@ -347,6 +447,12 @@ async function dxcc_lookup(call) {
347447 cqz : result . dxcc_cqz || null ,
348448 } ;
349449
450+ // Cache the result
451+ dxccCache . set ( call , {
452+ data : returner ,
453+ timestamp : Date . now ( )
454+ } ) ;
455+
350456 consecutiveErrorCount = 0 ; // Reset error count after a successful lookup
351457 abortController = null ; // Clear the abort controller after success
352458 return returner ;
@@ -373,15 +479,9 @@ async function dxcc_lookup(call) {
373479 * @returns {object } - The latest spot for the given frequency.
374480 */
375481function get_singlespot ( qrg ) {
376- let ret = { } ;
377- let youngest = Date . parse ( '1970-01-01T00:00:00.000Z' ) ;
378- spots . forEach ( ( single ) => {
379- if ( ( qrg * 1 === single . frequency ) && ( Date . parse ( single . when ) > youngest ) ) {
380- ret = single ;
381- youngest = Date . parse ( single . when ) ;
382- }
383- } ) ;
384- return ret ;
482+ // Use frequency index for O(1) lookup
483+ const spot = frequencyIndex . get ( qrg * 1 ) ;
484+ return spot || { } ;
385485}
386486
387487/**
@@ -390,7 +490,9 @@ function get_singlespot(qrg) {
390490 * @returns {array } - An array of spots for the given band.
391491 */
392492function get_bandspots ( band ) {
393- return spots . filter ( ( single ) => single . band === band ) ;
493+ // Use band index for O(1) lookup
494+ const spotSet = bandIndex . get ( band ) ;
495+ return spotSet ? Array . from ( spotSet ) : [ ] ;
394496}
395497
396498/**
@@ -399,7 +501,9 @@ function get_bandspots(band) {
399501 * @returns {array } - An array of spots for the given source.
400502 */
401503function get_sourcespots ( source ) {
402- return spots . filter ( ( single ) => single . source === source ) ;
504+ // Use source index for O(1) lookup
505+ const spotSet = sourceIndex . get ( source ) ;
506+ return spotSet ? Array . from ( spotSet ) : [ ] ;
403507}
404508
405509/**
@@ -438,22 +542,31 @@ function get_oldest(spotobj) {
438542 * @returns {array } - Deduplicated array of spots.
439543 */
440544function reduce_spots ( spotobject ) {
441- let unique = [ ] ;
545+ // Use a Map to track the latest spot for each unique combination
546+ // Key: spotted_continent_frequency, Value: spot object
547+ const latestSpots = new Map ( ) ;
548+
442549 spotobject . forEach ( ( single ) => {
443- if (
444- single . dxcc_spotter && // Ensure dxcc_spotter exists
445- single . dxcc_spotted && // Ensure dxcc_spotted exists
446- ! spotobject . find ( ( item ) =>
447- item . spotted === single . spotted &&
448- item . dxcc_spotter && item . dxcc_spotter . cont === single . dxcc_spotter . cont &&
449- item . frequency === single . frequency &&
450- Date . parse ( item . when ) > Date . parse ( single . when )
451- )
452- ) {
453- unique . push ( single ) ;
550+ // Skip spots without required DXCC data
551+ if ( ! single . dxcc_spotter || ! single . dxcc_spotted ) {
552+ return ;
553+ }
554+
555+ // Create unique key for deduplication
556+ const key = `${ single . spotted } _${ single . dxcc_spotter . cont } _${ single . frequency } ` ;
557+ const timestamp = Date . parse ( single . when ) ;
558+
559+ // Check if we already have a spot with this key
560+ const existing = latestSpots . get ( key ) ;
561+
562+ // Keep this spot if it's newer or if no existing spot
563+ if ( ! existing || timestamp > Date . parse ( existing . when ) ) {
564+ latestSpots . set ( key , single ) ;
454565 }
455566 } ) ;
456- return unique ;
567+
568+ // Convert Map values back to array
569+ return Array . from ( latestSpots . values ( ) ) ;
457570}
458571
459572/**
0 commit comments