@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/geo-maps",
3
- "version": "5.32.0",
3
+ "version": "5.33.0",
4
4
  "description": "MemberJunction: Framework-agnostic Leaflet map engine for point, heatmap, and choropleth rendering",
5
5
  "main": "src/map-core.js",
6
6
  "types": "types/index.d.ts",
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 to capture color and record per iteration
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
- _markerCount = renderChoroplethWithGeoResolver(_map, _markerLayer, _records, _config);
834
- } else if (_config.loadCountryData) {
835
- if (_countryCache) {
836
- _markerCount = renderChoroplethWithTextFallback(_map, _markerLayer, _records, _config, _countryCache);
837
- } else {
838
- _config.loadCountryData().then(function (countries) {
839
- _countryCache = countries;
840
- _markerCount = renderChoroplethWithTextFallback(_map, _markerLayer, _records, _config, _countryCache);
841
- notifyRenderComplete();
842
- }).catch(function () {
843
- _markerCount = renderChoroplethWithTextFallback(_map, _markerLayer, _records, _config, null);
844
- notifyRenderComplete();
845
- });
846
- return; // async notifyRenderComplete called in .then/.catch
847
- }
848
- } else {
849
- _markerCount = renderChoroplethWithTextFallback(_map, _markerLayer, _records, _config, null);
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
- /** GeoDataEngine-compatible resolver for coordinate-based choropleth. */
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;