@memberjunction/geo-maps 5.32.0 → 5.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/map-core.js +34 -123
- package/types/index.d.ts +8 -16
package/package.json
CHANGED
package/src/map-core.js
CHANGED
|
@@ -136,41 +136,6 @@ var MapCore = (function () {
|
|
|
136
136
|
return inside;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// ================================================================
|
|
140
|
-
// Country Name Matching (text-field fallback for choropleth)
|
|
141
|
-
// ================================================================
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Match a free-text country name to reference data via Name, ISO2, or CommonAliases.
|
|
145
|
-
* @param {Object[]} countries - Array of country records with Name, ISO2, CommonAliases
|
|
146
|
-
* @param {string} searchName
|
|
147
|
-
* @returns {Object|null}
|
|
148
|
-
*/
|
|
149
|
-
function findCountryMatch(countries, searchName) {
|
|
150
|
-
var normalized = searchName.trim().toLowerCase();
|
|
151
|
-
|
|
152
|
-
for (var i = 0; i < countries.length; i++) {
|
|
153
|
-
if (String(countries[i].Name || '').toLowerCase() === normalized) return countries[i];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
for (var i = 0; i < countries.length; i++) {
|
|
157
|
-
if (String(countries[i].ISO2 || '').toLowerCase() === normalized) return countries[i];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
for (var i = 0; i < countries.length; i++) {
|
|
161
|
-
var aliases = countries[i].CommonAliases;
|
|
162
|
-
if (!aliases) continue;
|
|
163
|
-
try {
|
|
164
|
-
var arr = JSON.parse(String(aliases));
|
|
165
|
-
for (var j = 0; j < arr.length; j++) {
|
|
166
|
-
if (arr[j].toLowerCase() === normalized) return countries[i];
|
|
167
|
-
}
|
|
168
|
-
} catch (e) { /* ignore parse errors */ }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
139
|
// ================================================================
|
|
175
140
|
// Popup HTML Construction
|
|
176
141
|
// ================================================================
|
|
@@ -453,16 +418,7 @@ var MapCore = (function () {
|
|
|
453
418
|
* Each record is expected to carry its own GeoJSON in config.boundaryField.
|
|
454
419
|
* Records without boundary data fall back to a centroid marker if lat/lng exist.
|
|
455
420
|
*/
|
|
456
|
-
var BOUNDARY_MAX_POLYGONS = 200;
|
|
457
|
-
|
|
458
421
|
function renderBoundary(map, layer, records, config) {
|
|
459
|
-
// Too many polygons causes stack overflow and browser freeze.
|
|
460
|
-
// Fall back to point markers when the dataset is too large.
|
|
461
|
-
if (records.length > BOUNDARY_MAX_POLYGONS) {
|
|
462
|
-
console.warn('[MapCore] Boundary mode: ' + records.length + ' records exceeds ' + BOUNDARY_MAX_POLYGONS + ' polygon limit, falling back to point markers');
|
|
463
|
-
return renderPointMarkers(map, layer, records, config);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
422
|
var boundaryField = config.boundaryField || 'BoundaryGeoJSON';
|
|
467
423
|
var latField = config.latitudeField || '__mj_Latitude';
|
|
468
424
|
var lngField = config.longitudeField || '__mj_Longitude';
|
|
@@ -482,7 +438,9 @@ var MapCore = (function () {
|
|
|
482
438
|
var geojson = typeof boundaryRaw === 'string'
|
|
483
439
|
? JSON.parse(boundaryRaw) : boundaryRaw;
|
|
484
440
|
|
|
485
|
-
// IIFE
|
|
441
|
+
// IIFE: capture color/record/name/id per iteration. Without it,
|
|
442
|
+
// mouseout/getBounds reference loop-shared variables and use the
|
|
443
|
+
// last iteration's values for every polygon.
|
|
486
444
|
(function (rec, recName, recColor, recId) {
|
|
487
445
|
var baseStyle = {
|
|
488
446
|
fillColor: recColor,
|
|
@@ -666,61 +624,6 @@ var MapCore = (function () {
|
|
|
666
624
|
return bounds.length;
|
|
667
625
|
}
|
|
668
626
|
|
|
669
|
-
// ================================================================
|
|
670
|
-
// Rendering: Choropleth — text-field fallback (without GeoResolver)
|
|
671
|
-
// ================================================================
|
|
672
|
-
|
|
673
|
-
function renderChoroplethWithTextFallback(map, layer, records, config, countryCache) {
|
|
674
|
-
var latField = config.latitudeField || '__mj_Latitude';
|
|
675
|
-
var lngField = config.longitudeField || '__mj_Longitude';
|
|
676
|
-
var countryField = config.countryField || 'Country';
|
|
677
|
-
var colors = config.colors || DEFAULT_COLORS;
|
|
678
|
-
var bounds = [];
|
|
679
|
-
var recordsByCountry = {};
|
|
680
|
-
|
|
681
|
-
for (var i = 0; i < records.length; i++) {
|
|
682
|
-
var record = records[i];
|
|
683
|
-
var lat = getNumericField(record, latField);
|
|
684
|
-
var lng = getNumericField(record, lngField);
|
|
685
|
-
if (lat == null || lng == null || isNaN(lat) || isNaN(lng)) continue;
|
|
686
|
-
bounds.push(L.latLng(lat, lng));
|
|
687
|
-
|
|
688
|
-
var country = String(getField(record, countryField) || 'Unknown');
|
|
689
|
-
if (!recordsByCountry[country]) recordsByCountry[country] = [];
|
|
690
|
-
recordsByCountry[country].push(record);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
var countryNames = Object.keys(recordsByCountry);
|
|
694
|
-
var colorIdx = 0;
|
|
695
|
-
|
|
696
|
-
for (var ci = 0; ci < countryNames.length; ci++) {
|
|
697
|
-
var countryName = countryNames[ci];
|
|
698
|
-
var countryRecords = recordsByCountry[countryName];
|
|
699
|
-
var color = colors[colorIdx % colors.length];
|
|
700
|
-
var rendered = false;
|
|
701
|
-
|
|
702
|
-
if (countryCache) {
|
|
703
|
-
var countryData = findCountryMatch(countryCache, countryName);
|
|
704
|
-
if (countryData && countryData.BoundaryGeoJSON) {
|
|
705
|
-
rendered = renderBoundaryRegion(
|
|
706
|
-
layer, countryName, countryData.BoundaryGeoJSON,
|
|
707
|
-
countryRecords, color, 'country', config
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (!rendered) {
|
|
713
|
-
// Circle fallback at centroid of records
|
|
714
|
-
renderCircleFallback(layer, countryName, countryRecords, color, config);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
colorIdx++;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
fitBounds(map, bounds, config.maxZoom);
|
|
721
|
-
return bounds.length;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
627
|
// ================================================================
|
|
725
628
|
// MapEngine Factory
|
|
726
629
|
// ================================================================
|
|
@@ -734,9 +637,7 @@ var MapCore = (function () {
|
|
|
734
637
|
* @param {number} [config.zoom] - Initial zoom level
|
|
735
638
|
* @param {string} [config.latitudeField] - Latitude field name (default '__mj_Latitude')
|
|
736
639
|
* @param {string} [config.longitudeField] - Longitude field name (default '__mj_Longitude')
|
|
737
|
-
* @param {Object} [config.geoResolver] - GeoDataEngine-compatible resolver with ResolvePointToLocation()
|
|
738
|
-
* @param {string} [config.countryField] - Country field name for text-field fallback (default 'Country')
|
|
739
|
-
* @param {Function} [config.loadCountryData] - Async function returning country reference data
|
|
640
|
+
* @param {Object} [config.geoResolver] - GeoDataEngine-compatible resolver with ResolvePointToLocation(). Required for choropleth (Regions) mode.
|
|
740
641
|
* @param {Function} [config.getRecordId] - Returns composite PK string for a record
|
|
741
642
|
* @param {Function} [config.getRecordName] - Returns display name for a record
|
|
742
643
|
* @param {Function} [config.onMarkerClick] - Callback when a point marker is clicked
|
|
@@ -760,7 +661,6 @@ var MapCore = (function () {
|
|
|
760
661
|
var _records = [];
|
|
761
662
|
var _mode = 'point';
|
|
762
663
|
var _markerCount = 0;
|
|
763
|
-
var _countryCache = null;
|
|
764
664
|
var _map = null;
|
|
765
665
|
var _markerLayer = null;
|
|
766
666
|
|
|
@@ -829,25 +729,37 @@ var MapCore = (function () {
|
|
|
829
729
|
}
|
|
830
730
|
|
|
831
731
|
function renderChoroplethDispatch() {
|
|
832
|
-
if (_config.geoResolver) {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
732
|
+
if (!_config.geoResolver) {
|
|
733
|
+
// No text-fallback — choropleth requires coordinate-based resolution.
|
|
734
|
+
console.warn('[MapCore] Choropleth (Regions) mode requires config.geoResolver. Nothing rendered.');
|
|
735
|
+
_markerCount = 0;
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// GeoDataEngine is on-demand load — without this await the first
|
|
740
|
+
// resolve returns empty. Loaded check avoids re-awaiting after the
|
|
741
|
+
// initial parse.
|
|
742
|
+
var resolver = _config.geoResolver;
|
|
743
|
+
var ensureLoaded = typeof resolver.EnsureLoaded === 'function'
|
|
744
|
+
? resolver.EnsureLoaded.bind(resolver)
|
|
745
|
+
: null;
|
|
746
|
+
var alreadyLoaded = ensureLoaded ? resolver.Loaded === true : true;
|
|
747
|
+
|
|
748
|
+
if (ensureLoaded && !alreadyLoaded) {
|
|
749
|
+
ensureLoaded().then(function () {
|
|
750
|
+
if (!_map || !_markerLayer) return;
|
|
751
|
+
_markerCount = renderChoroplethWithGeoResolver(_map, _markerLayer, _records, _config);
|
|
752
|
+
notifyRenderComplete();
|
|
753
|
+
}).catch(function (err) {
|
|
754
|
+
console.warn('[MapCore] geoResolver.EnsureLoaded() failed', err);
|
|
755
|
+
if (!_map || !_markerLayer) return;
|
|
756
|
+
_markerCount = renderChoroplethWithGeoResolver(_map, _markerLayer, _records, _config);
|
|
757
|
+
notifyRenderComplete();
|
|
758
|
+
});
|
|
759
|
+
return; // async — notifyRenderComplete called in the chain above
|
|
850
760
|
}
|
|
761
|
+
|
|
762
|
+
_markerCount = renderChoroplethWithGeoResolver(_map, _markerLayer, _records, _config);
|
|
851
763
|
}
|
|
852
764
|
|
|
853
765
|
function notifyRenderComplete() {
|
|
@@ -912,7 +824,6 @@ var MapCore = (function () {
|
|
|
912
824
|
createEngine: createEngine,
|
|
913
825
|
spatialCluster: spatialCluster,
|
|
914
826
|
pointInPolygon: pointInPolygon,
|
|
915
|
-
findCountryMatch: findCountryMatch,
|
|
916
827
|
VERSION: '1.0.0'
|
|
917
828
|
};
|
|
918
829
|
})();
|
package/types/index.d.ts
CHANGED
|
@@ -17,12 +17,11 @@ export interface MapConfig {
|
|
|
17
17
|
latitudeField?: string;
|
|
18
18
|
/** Longitude field name. Defaults to '__mj_Longitude'. */
|
|
19
19
|
longitudeField?: string;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* GeoDataEngine-compatible resolver. Required for choropleth (Regions) mode —
|
|
22
|
+
* no text-field fallback, records must carry pre-geocoded lat/lng.
|
|
23
|
+
*/
|
|
21
24
|
geoResolver?: GeoResolver;
|
|
22
|
-
/** Country field name for text-field fallback choropleth. Defaults to 'Country'. */
|
|
23
|
-
countryField?: string;
|
|
24
|
-
/** Async function returning country reference data (for text-field fallback). */
|
|
25
|
-
loadCountryData?: () => Promise<CountryData[]>;
|
|
26
25
|
/** Returns a composite primary key string for a record. */
|
|
27
26
|
getRecordId?: (record: Record<string, unknown>) => string;
|
|
28
27
|
/** Returns a display name for a record. */
|
|
@@ -54,6 +53,10 @@ export interface MapConfig {
|
|
|
54
53
|
/** GeoDataEngine-compatible resolver interface. */
|
|
55
54
|
export interface GeoResolver {
|
|
56
55
|
ResolvePointToLocation(lat: number, lng: number): GeoPointResolution;
|
|
56
|
+
/** Awaited before the first choropleth resolve when the resolver lazy-loads. */
|
|
57
|
+
EnsureLoaded?: () => Promise<void>;
|
|
58
|
+
/** When true, MapCore skips the EnsureLoaded await on the fast path. */
|
|
59
|
+
Loaded?: boolean;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
/** Result of resolving a coordinate to geographic regions. */
|
|
@@ -62,14 +65,6 @@ export interface GeoPointResolution {
|
|
|
62
65
|
State?: { ID: string; Name: string; BoundaryGeoJSON?: string | null } | undefined;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
/** Country reference data for text-field fallback choropleth. */
|
|
66
|
-
export interface CountryData {
|
|
67
|
-
Name: string;
|
|
68
|
-
ISO2?: string;
|
|
69
|
-
BoundaryGeoJSON?: string | null;
|
|
70
|
-
CommonAliases?: string | null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
68
|
/** Emitted when a point marker is clicked. */
|
|
74
69
|
export interface MarkerClickEvent {
|
|
75
70
|
recordId: string;
|
|
@@ -135,8 +130,5 @@ export function spatialCluster(
|
|
|
135
130
|
/** Ray-casting point-in-polygon test. Ring uses GeoJSON [lng, lat] pairs. */
|
|
136
131
|
export function pointInPolygon(lat: number, lng: number, ring: Array<[number, number]>): boolean;
|
|
137
132
|
|
|
138
|
-
/** Match a free-text country name to reference data via Name, ISO2, or CommonAliases. */
|
|
139
|
-
export function findCountryMatch(countries: CountryData[], searchName: string): CountryData | null;
|
|
140
|
-
|
|
141
133
|
/** Library version. */
|
|
142
134
|
export const VERSION: string;
|