@sean_kenny/coloured-bitmap-to-geojson-polygons-js
Advanced tools
Comparing version 0.1.0 to 0.1.1
@@ -7,4 +7,5 @@ export declare enum BitsPerPixel { | ||
SIXTEEN_BIT_RGB = "SIXTEEN_BIT_RGB", | ||
TWENTY_FOUR_BIT_RGB = "TWENTY_FOUR_BIT_RGB" | ||
TWENTY_FOUR_BIT_RGB = "TWENTY_FOUR_BIT_RGB", | ||
THIRTY_TWO_BIT_ALPHA_PLUS_RGB = "THIRTY_TWO_BIT_ALPHA_PLUS_RGB" | ||
} | ||
export declare const hexValueToBitsPerPixel: Record<number, BitsPerPixel | undefined>; |
@@ -9,2 +9,3 @@ export var BitsPerPixel; | ||
BitsPerPixel["TWENTY_FOUR_BIT_RGB"] = "TWENTY_FOUR_BIT_RGB"; | ||
BitsPerPixel["THIRTY_TWO_BIT_ALPHA_PLUS_RGB"] = "THIRTY_TWO_BIT_ALPHA_PLUS_RGB"; | ||
})(BitsPerPixel || (BitsPerPixel = {})); | ||
@@ -18,2 +19,3 @@ export const hexValueToBitsPerPixel = { | ||
24: BitsPerPixel.TWENTY_FOUR_BIT_RGB, | ||
32: BitsPerPixel.THIRTY_TWO_BIT_ALPHA_PLUS_RGB | ||
}; |
@@ -37,2 +37,3 @@ import { BitsPerPixel } from "./bits-per-pixel.js"; | ||
header: BmpFileMetadataHeader; | ||
headerSizeBytes: 14; | ||
} | ||
@@ -42,5 +43,7 @@ export declare const extractHeaderFromHexBmpData: (input: ExtractHeaderFromHexBmpDataInput) => ExtractHeaderFromHexBmpDataOutput; | ||
hexBmpData: string; | ||
headerSizeBytes: 14; | ||
} | ||
interface ExtractInfoHeaderFromHexBmpDataOutput { | ||
infoHeader: BmpFileMetadataInfoHeader; | ||
infoHeaderSizeBytes: number; | ||
} | ||
@@ -50,2 +53,4 @@ export declare const extractInfoHeaderFromHexBmpData: (input: ExtractInfoHeaderFromHexBmpDataInput) => ExtractInfoHeaderFromHexBmpDataOutput; | ||
infoHeader: Pick<BmpFileMetadataInfoHeader, 'bitsPerPixel' | 'numberOfColoursUsed'>; | ||
infoHeaderSizeBytes: number; | ||
headerSizeBytes: 14; | ||
hexBmpData: string; | ||
@@ -55,5 +60,5 @@ } | ||
colourMap: BmpFileColourMap; | ||
bytesUsedForColourMap: number; | ||
colourMapSizeBytes: number; | ||
} | ||
export declare const extractColourMapFromHexBmpData: (input: ExtractColourMapFromHexBmpDataInput) => ExtractColourMapFromHexBmpDataOutput; | ||
export {}; |
@@ -10,19 +10,25 @@ import { BitsPerPixel, hexValueToBitsPerPixel } from "./bits-per-pixel.js"; | ||
dataOffsetBytes: hexStringToNumericValue(input.hexBmpData.slice(20, 28)), | ||
} | ||
}, | ||
headerSizeBytes: 14 | ||
}); | ||
export const extractInfoHeaderFromHexBmpData = (input) => ({ | ||
infoHeader: { | ||
infoHeaderSizeBytes: hexStringToNumericValue(input.hexBmpData.slice(28, 36)), | ||
bmpWidthPx: hexStringToNumericValue(input.hexBmpData.slice(36, 44)), | ||
bmpHeightPx: hexStringToNumericValue(input.hexBmpData.slice(44, 52)), | ||
numberOfPlanes: hexStringToNumericValue(input.hexBmpData.slice(52, 56)), | ||
bitsPerPixel: hexValueToBitsPerPixel[hexStringToNumericValue(input.hexBmpData.slice(56, 60))] ?? 'UNKNOWN', | ||
compressionType: hexValueToCompressionType[hexStringToNumericValue(input.hexBmpData.slice(60, 68))] ?? 'UNKNOWN', | ||
imageCompressedSizeBytes: hexStringToNumericValue(input.hexBmpData.slice(68, 76)), | ||
horizontalPixelsPerMetre: hexStringToNumericValue(input.hexBmpData.slice(76, 84)), | ||
verticalPixelsPerMetre: hexStringToNumericValue(input.hexBmpData.slice(84, 92)), | ||
numberOfColoursUsed: hexStringToNumericValue(input.hexBmpData.slice(92, 100)), | ||
numberOfImportantColours: hexStringToNumericValue(input.hexBmpData.slice(100, 108)), | ||
} | ||
}); | ||
export const extractInfoHeaderFromHexBmpData = (input) => { | ||
const startIndex = input.headerSizeBytes * 2; | ||
const infoHeaderSizeBytes = hexStringToNumericValue(input.hexBmpData.slice(startIndex, startIndex + 8)); | ||
return { | ||
infoHeader: { | ||
infoHeaderSizeBytes, | ||
bmpWidthPx: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 8, startIndex + 16)), | ||
bmpHeightPx: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 16, startIndex + 24)), | ||
numberOfPlanes: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 24, startIndex + 28)), | ||
bitsPerPixel: hexValueToBitsPerPixel[hexStringToNumericValue(input.hexBmpData.slice(startIndex + 28, startIndex + 32))] ?? 'UNKNOWN', | ||
compressionType: hexValueToCompressionType[hexStringToNumericValue(input.hexBmpData.slice(startIndex + 32, startIndex + 40))] ?? 'UNKNOWN', | ||
imageCompressedSizeBytes: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 40, startIndex + 48)), | ||
horizontalPixelsPerMetre: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 48, startIndex + 56)), | ||
verticalPixelsPerMetre: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 56, startIndex + 64)), | ||
numberOfColoursUsed: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 64, startIndex + 72)), | ||
numberOfImportantColours: hexStringToNumericValue(input.hexBmpData.slice(startIndex + 72, startIndex + 80)), | ||
}, | ||
infoHeaderSizeBytes | ||
}; | ||
}; | ||
export const extractColourMapFromHexBmpData = (input) => { | ||
@@ -38,8 +44,8 @@ const { bitsPerPixel, numberOfColoursUsed } = input.infoHeader; | ||
colourMap: {}, | ||
bytesUsedForColourMap: 0 | ||
colourMapSizeBytes: 0 | ||
}; | ||
} | ||
const bytesUsedForColourMap = 4 * numberOfColoursUsed; | ||
const startIndexInHexBmpData = 108; | ||
const endIndexInHexBmpData = startIndexInHexBmpData + bytesUsedForColourMap; | ||
const colourMapSizeBytes = 4 * numberOfColoursUsed; | ||
const startIndexInHexBmpData = (input.infoHeaderSizeBytes + input.headerSizeBytes) * 2; | ||
const endIndexInHexBmpData = startIndexInHexBmpData + colourMapSizeBytes; | ||
let currentIndex = 0; | ||
@@ -66,4 +72,4 @@ let currentIndexInHexBmpData = startIndexInHexBmpData; | ||
colourMap, | ||
bytesUsedForColourMap | ||
colourMapSizeBytes | ||
}; | ||
}; |
@@ -5,4 +5,2 @@ import { BmpFileMetadata } from "./bmp-file-metadata.js"; | ||
inputFilePath: string; | ||
bitmapWidthPx?: number | undefined; | ||
bitmapHeightPx?: number | undefined; | ||
colourToPropertiesMap: Record<string, TData>; | ||
@@ -9,0 +7,0 @@ } |
import { readFile } from "fs/promises"; | ||
import { extractColourMapFromHexBmpData, extractHeaderFromHexBmpData, extractInfoHeaderFromHexBmpData } from "./bmp-file-metadata.js"; | ||
import { extractImageDataFromHexBmpData } from "./image-data.js"; | ||
import { determineBitmapWidthAndHeight } from "./bitmap-width-and-height.js"; | ||
import { consolidateImageDataIntoPolygons } from "./image-data-to-polygons.js"; | ||
import { extractImageDataFromHexBmpData } from "./image-data/image-data.js"; | ||
import { consolidateImageDataIntoPolygons } from "./image-data-to-polygons/image-data-to-polygons.js"; | ||
export const exportColouredBitmapToGeoJSONPolygons = async (input) => { | ||
const bmpData = await readFile(input.inputFilePath); | ||
const hexBmpData = bmpData.toString('hex'); | ||
const { header } = extractHeaderFromHexBmpData({ hexBmpData }); | ||
const { infoHeader } = extractInfoHeaderFromHexBmpData({ hexBmpData }); | ||
const { colourMap, bytesUsedForColourMap } = extractColourMapFromHexBmpData({ hexBmpData, infoHeader }); | ||
const { bitmapWidthPx, bitmapHeightPx } = determineBitmapWidthAndHeight({ | ||
const { header, headerSizeBytes } = extractHeaderFromHexBmpData({ hexBmpData }); | ||
const { infoHeader, infoHeaderSizeBytes } = extractInfoHeaderFromHexBmpData({ hexBmpData, headerSizeBytes }); | ||
const { colourMap, colourMapSizeBytes } = extractColourMapFromHexBmpData({ | ||
hexBmpData, | ||
infoHeader, | ||
bitmapWidthPxOverride: input.bitmapWidthPx, | ||
bitmapHeightPxOverride: input.bitmapHeightPx, | ||
headerSizeBytes, | ||
infoHeaderSizeBytes | ||
}); | ||
@@ -20,8 +19,13 @@ const { imageData, allColoursPresent } = extractImageDataFromHexBmpData({ | ||
colourMap, | ||
bitmapWidthPx, | ||
bitmapHeightPx, | ||
bytesUsedForColourMap | ||
bitmapWidthPx: infoHeader.bmpWidthPx, | ||
bitmapHeightPx: infoHeader.bmpHeightPx, | ||
bitsPerPixel: infoHeader.bitsPerPixel, | ||
headerSizeBytes, | ||
infoHeaderSizeBytes, | ||
colourMapSizeBytes | ||
}); | ||
const { polygons } = consolidateImageDataIntoPolygons({ | ||
imageData, | ||
bitmapWidthPx: infoHeader.bmpWidthPx, | ||
bitmapHeightPx: infoHeader.bmpHeightPx, | ||
allColoursPresent, | ||
@@ -28,0 +32,0 @@ colourToPropertiesMap: input.colourToPropertiesMap |
@@ -9,4 +9,4 @@ export const hexStringtoAsciiString = (hexString) => { | ||
?.reverse() | ||
?.join() ?? ''; | ||
?.join('') ?? ''; | ||
return parseInt(bigEndianHexString, 16); | ||
}; |
{ | ||
"name": "@sean_kenny/coloured-bitmap-to-geojson-polygons-js", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "A library for convering colour segmented bitmaps to a GeoJSON file of polygons.", | ||
@@ -5,0 +5,0 @@ "scripts": { |
120
README.md
# coloured-bitmap-to-geojson-js | ||
coloured-bitmap-to-geojson-js | ||
This is a TypeScript library which contains a function which converts `.bmp` bitmap files into a GeoJSON file. | ||
[![Version](https://img.shields.io/npm/v/@sean_kenny/coloured-bitmap-to-geojson-polygons-js.svg)](https://www.npmjs.com/package/@sean_kenny/coloured-bitmap-to-geojson-polygons-js) | ||
[![Downloads/week](https://img.shields.io/npm/dw/@sean_kenny/coloured-bitmap-to-geojson-polygons-js.svg)](https://www.npmjs.com/package/@sean_kenny/coloured-bitmap-to-geojson-polygons-js) | ||
## Installation and Usage | ||
``` | ||
$ npm install --save-dev @sean_kenny/coloured-bitmap-to-geojson-polygons-js | ||
# or | ||
$ yarn add -D @sean_kenny/coloured-bitmap-to-geojson-polygons-js | ||
``` | ||
An output can then be generated by calling the `exportColouredBitmapToGeoJSONPolygons` function, which is the only function in the library. `colourToPropertiesMap` is an argument which can be used to attach properties (besides `colourHexCode`) to regions in your bitmap of a particular colour. Keys in `colourToPropertiesMap` must be lowercase (Ex. `ff0000` rather than `FF0000`). | ||
``` | ||
const { outputGeoJSON, bmpFileMetadata } = exportColouredBitmapToGeoJSONPolygons({ | ||
inputFilePath: '/path/to/your/file/your_file.bmp' | ||
colourToPropertiesMap: { | ||
"#ff0000": { | ||
zone: "The red zone" | ||
}, | ||
"#00ff00": { | ||
zone: "The green zone" | ||
}, | ||
} | ||
}) | ||
``` | ||
`bmpFileMetadata` will contain some data in the following format: | ||
``` | ||
{ | ||
"header": { | ||
"signature": "BM", | ||
"fileSizeBytes": 120054, | ||
"reservedField": "00000000", | ||
"dataOffsetBytes": 54 | ||
}, | ||
"infoHeader": { | ||
"bitsPerPixel": "TWENTY_FOUR_BIT_RGB", | ||
"bmpHeightPx": 200, | ||
"bmpWidthPx": 200, | ||
"compressionType": "BI_RGB", | ||
"horizontalPixelsPerMetre": 0, | ||
"imageCompressedSizeBytes": 0, | ||
"infoHeaderSizeBytes": 40, | ||
"numberOfColoursUsed": 0, | ||
"numberOfImportantColours": 0, | ||
"numberOfPlanes": 1, | ||
"verticalPixelsPerMetre": 0 | ||
}, | ||
"colourMap": {} | ||
} | ||
``` | ||
And `outputGeoJSON` will contain some data in the following format: | ||
``` | ||
{ | ||
"type": "GeometryCollection", | ||
"geometries": [ | ||
{ | ||
"type": "Polygon", | ||
"coordinates": [ | ||
[ | ||
[-180, 90], | ||
[-180, -90], | ||
... | ||
] | ||
], | ||
"properties": { | ||
"colourHexCode": "#ff0000", | ||
"data": { "zone": "The red zone" } | ||
} | ||
}, | ||
{ | ||
"type": "Polygon", | ||
"coordinates": [ | ||
[ | ||
[-176.4, 90], | ||
[-176.4, 89.1], | ||
... | ||
] | ||
], | ||
"properties": { | ||
"colourHexCode": "#00ff00", | ||
"data": { "zone": "The green zone" } | ||
} | ||
}, | ||
{ | ||
"type": "Polygon", | ||
"coordinates": [ | ||
[ | ||
[1.8000000000000114, 1.7999999999999972], | ||
[1.8000000000000114, 0], | ||
... | ||
] | ||
], | ||
"properties": { | ||
"colourHexCode": "#0000ff", | ||
"data": null | ||
} | ||
} | ||
] | ||
} | ||
``` | ||
## Some sample inputs and outputs | ||
Original BMP file | Output GeoJSON file | ||
:-------------------------:|:-------------------------: | ||
![](https://github.com/SeanKennyNF/coloured-bitmap-to-geojson-polygons-js/blob/main/readme-images/bmp-24.png) | ![](https://github.com/SeanKennyNF/coloured-bitmap-to-geojson-polygons-js/blob/main/readme-images/bmp-24-output.png) | ||
![](https://github.com/SeanKennyNF/coloured-bitmap-to-geojson-polygons-js/blob/main/readme-images/provinces-subset-2.png) | ![](https://github.com/SeanKennyNF/coloured-bitmap-to-geojson-polygons-js/blob/main/readme-images/provinces-subset-2-output.png) | ||
## Acknowledgements | ||
There's some interesting reading about the exact structure of a bitmap file [here](https://en.wikipedia.org/wiki/BMP_file_format) which is what a lot of this library is based on. | ||
All files whose name follows the pattern `provinces-XXXXXXX.bmp` which are used for testing are pulled directly from the `ab937cf899b75f74662a17574bd0d46072793beb` version of [Anbennar](https://bitbucket.org/JayBean/anbennar-eu4-fork-public-build) which was published to Bitbucket on August 23rd 2024. All credit for those bitmaps goes to their creator. ``bmp-24.bmp` was generously made public by the [University of South Carolina]('https://people.math.sc.edu/Burkardt/data/bmp/bmp.html'). | ||
You're welcome to use this library for anything you'd like. Please keep in mind this hasn't been thoroughly tested with a large variety of bitmap files so this may or may not work for your use case. Please reach out if you have a bitmap file which is not processed properly by this library and I'd be happy to work on something to make it work for you. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
54750
36
1150
120