diff --git a/README.md b/README.md index c0d642d..99a9e66 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,38 @@ If you want to use Leaflet.AutoGraticule directly inside a website without using ``` ## Options - +All options are optional and any passed option will override the corresponding default option listed below. ```javascript const options = { /** Leaflet map event on which to redraw the graticule. */ redraw: 'moveend', /** Minimum distance in pixels between two graticule lines. */ - minDistance: 100 + minDistance: 100, + + /** Format of labels for every graticule, options: + * - degrees - Decimal degrees, examples: -13.37°, 0.1°, + * - dms - Degrees-Minutes-Seconds, examples: 13°22'12.00''W, 00°00'36.00''E + */ + labelFormat: 'degrees', + + /** Size of rendered graticule labels, options: + * - normal + * - large - css font-size: 1.25em + */ + labelSize: 'normal', + + /** Style for graticule lines, accepts a Partial object. + * Overrides the default values with the provided fields, while keeping + * defaults for all unspecified fields. + */ + lineStyle: { + stroke: true, + color: '#111', + opacity: 0.6, + weight: 1, + interactive: false + } }; new L.AutoGraticule(options).addTo(map); diff --git a/package.json b/package.json index 7ae572f..b0b489d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leaflet-auto-graticule", - "version": "2.0.0", + "version": "2.1.0", "description": "Leaflet graticule with automatic adaption to zoom level.", "type": "module", "main": "./dist/L.AutoGraticule.js", diff --git a/src/L.AutoGraticule.css b/src/L.AutoGraticule.css index 9a72ff8..4243d0d 100644 --- a/src/L.AutoGraticule.css +++ b/src/L.AutoGraticule.css @@ -9,3 +9,7 @@ padding-left:2px; text-shadow: -2px 0 #FFFFFF, 0 2px #FFFFFF, 2px 0 #FFFFFF, 0 -2px #FFFFFF; } + +.leaflet-grid-label-large { + font-size: 1.25em; +} diff --git a/src/L.AutoGraticule.ts b/src/L.AutoGraticule.ts index 3813fe8..b73a720 100644 --- a/src/L.AutoGraticule.ts +++ b/src/L.AutoGraticule.ts @@ -6,21 +6,36 @@ export interface AutoGraticuleOptions extends LayerOptions { /** Minimum distance between two lines in pixels */ minDistance: number + + /** + * Format of labels for every graticule, options: + * - degrees: Decimal degrees, examples: -13.37°, 0.1° + * - dms: Degrees-Minutes-Seconds, examples: 13°22'12.00''W, 00°00'36.00''E + */ + labelFormat: 'dms' | 'degrees' + + /** + * Size of rendered graticule degrees, large == font-size: 1.25em + */ + labelSize: 'normal' | 'large' + + lineStyle: Partial; } export default class AutoGraticule extends LayerGroup { options: AutoGraticuleOptions = { redraw: 'moveend', - minDistance: 100 // Minimum distance between two lines in pixels - }; - - lineStyle: PolylineOptions = { - stroke: true, - color: '#111', - opacity: 0.6, - weight: 1, - interactive: false + minDistance: 100, // Minimum distance between two lines in pixels + labelFormat: 'degrees', + labelSize: 'normal', + lineStyle: { + stroke: true, + color: '#111', + opacity: 0.6, + weight: 1, + interactive: false + } }; _bounds!: LatLngBounds; @@ -28,7 +43,13 @@ export default class AutoGraticule extends LayerGroup { constructor(options?: Partial) { super(); - Util.setOptions(this, options); + Util.setOptions(this, { + ...options, + lineStyle: { + ...this.options.lineStyle, + ...options?.lineStyle + } + }); } @@ -129,35 +150,64 @@ export default class AutoGraticule extends LayerGroup { const bottomLL = new LatLng(this._bounds.getSouth(), x); const topLL = new LatLng(this._bounds.getNorth(), x); - return new Polyline([bottomLL, topLL], this.lineStyle); + return new Polyline([bottomLL, topLL], this.options.lineStyle); } buildYLine(y: number): L.Polyline { const leftLL = new LatLng(y, this._bounds.getWest()); const rightLL = new LatLng(y, this._bounds.getEast()); - return new Polyline([leftLL, rightLL], this.lineStyle); + return new Polyline([leftLL, rightLL], this.options.lineStyle); } buildLabel(axis: 'gridlabel-horiz' | 'gridlabel-vert', val: number) { const bounds = this._map.getBounds().pad(-0.003); + let className: string = 'leaflet-grid-label'; let latLng: LatLng; + let label: string; + if (axis == 'gridlabel-horiz') { latLng = new LatLng(bounds.getNorth(), val); } else { latLng = new LatLng(val, bounds.getWest()); } + if (this.options.labelFormat === 'degrees') { + label = `${val} °`; + } else if (this.options.labelFormat === 'dms') { + label = AutoGraticule.decimalLatLngToDMSStr(axis, val); + } else { + throw new Error(`Unhandled labelFormat setting: ${this.options.labelFormat}`); + } + + if (this.options.labelSize === 'large') { + className += ' leaflet-grid-label-large'; + } + return marker(latLng, { interactive: false, icon: divIcon({ iconSize: [0, 0], - className: 'leaflet-grid-label', - html: '
' + val + ' °
' + className, + html: '
' + label + '
' }) }); } + static decimalLatLngToDMSStr(axis: 'gridlabel-horiz' | 'gridlabel-vert', val: number) { + const suffix = axis === 'gridlabel-horiz' + ? val < 0 ? 'W' : 'E' + : val < 0 ? 'S' : 'N'; + + const decimalAbsDegrees = Math.abs(val); + const degrees = Math.trunc(decimalAbsDegrees); + const fraction = decimalAbsDegrees - degrees; + const minutes = Math.trunc(fraction * 60); + const seconds = (fraction - minutes / 60) * Math.pow(60, 2); + + return `${degrees.toString().padStart(2, '0')}°${minutes.toString().padStart(2, '0')}'${seconds.toFixed(2).padStart(5, '0')}''${suffix}`; + } + /** * Rounds the given number to a fixed number of decimals in order to avoid floating point inaccuracies * (for example to make 0.1 + 0.2 = 0.3 instead of 0.30000000000000004). diff --git a/src/__tests__/L.Autograticule.test.ts b/src/__tests__/L.Autograticule.test.ts index d2d0738..a8e8a4d 100644 --- a/src/__tests__/L.Autograticule.test.ts +++ b/src/__tests__/L.Autograticule.test.ts @@ -47,4 +47,36 @@ test('niceRound', () => { expect(AutoGraticule.getGridDivisor(10, true)).toBe(5); expect(AutoGraticule.getGridDivisor(5.1, true)).toBe(5); expect(AutoGraticule.getGridDivisor(5, true)).toBe(5); -}); \ No newline at end of file +}); + +test('decimalLatLngToDMSStr', () => { + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -180.00)).toBe("180°00'00.00''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -100.00)).toBe("100°00'00.00''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -13.37331)).toBe("13°22'23.92''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -13.37)).toBe("13°22'12.00''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -5.00)).toBe("05°00'00.00''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -1.00)).toBe("01°00'00.00''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -0.10)).toBe("00°06'00.00''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -0.01)).toBe("00°00'36.00''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', -0.001)).toBe("00°00'03.60''W"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 0.00)).toBe("00°00'00.00''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 0.001)).toBe("00°00'03.60''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 0.01)).toBe("00°00'36.00''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 0.10)).toBe("00°06'00.00''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 1.00)).toBe("01°00'00.00''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 5.00)).toBe("05°00'00.00''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 13.37)).toBe("13°22'12.00''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 13.37331)).toBe("13°22'23.92''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 100.00)).toBe("100°00'00.00''E"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-horiz', 180.00)).toBe("180°00'00.00''E"); + + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', -85.05115)).toBe("85°03'04.14''S"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', -13.020381)).toBe("13°01'13.37''S"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', -10.23456)).toBe("10°14'04.42''S"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', -0.00002)).toBe("00°00'00.07''S"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', 0.00)).toBe("00°00'00.00''N"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', 0.00002)).toBe("00°00'00.07''N"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', 10.23456)).toBe("10°14'04.42''N"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', 13.020381)).toBe("13°01'13.37''N"); + expect(AutoGraticule.decimalLatLngToDMSStr('gridlabel-vert', 85.00)).toBe("85°00'00.00''N"); +});