Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<L.PolylineOptions> 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);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 4 additions & 0 deletions src/L.AutoGraticule.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
78 changes: 64 additions & 14 deletions src/L.AutoGraticule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,50 @@ 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<PolylineOptions>;
}

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;


constructor(options?: Partial<AutoGraticuleOptions>) {
super();
Util.setOptions(this, options);
Util.setOptions(this, {
...options,
lineStyle: {
...this.options.lineStyle,
...options?.lineStyle
}
});
}


Expand Down Expand Up @@ -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}&#8239;°`;
} 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: '<div class="' + axis + '">' + val + '&#8239;°</div>'
className,
html: '<div class="' + axis + '">' + label + '</div>'
})
});
}

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).
Expand Down
34 changes: 33 additions & 1 deletion src/__tests__/L.Autograticule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

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");
});