summaryrefslogtreecommitdiff
path: root/src/Api/api.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/Api/api.ts')
-rw-r--r--src/Api/api.ts251
1 files changed, 251 insertions, 0 deletions
diff --git a/src/Api/api.ts b/src/Api/api.ts
new file mode 100644
index 0000000..2da4594
--- /dev/null
+++ b/src/Api/api.ts
@@ -0,0 +1,251 @@
+import { Region, LoadedResult, BoundingBox, TileParameters, GetEntitiesResponse, GetPortalResponse } from "./interfaces";
+import { Portal, Field, Link } from "./types";
+import { decodeEntity } from "./entityDecoder";
+
+const baseHeaders = {
+ 'Content-Type': 'application/json; charset=UTF-8',
+ 'Pragma': 'no-cache',
+ 'Accept': '*/*',
+ 'Host': 'intel.ingress.com',
+ 'Accept-Language': 'ru',
+ 'Cache-Control': 'no-cache',
+ 'Accept-Encoding': 'br, gzip, deflate',
+ 'Origin': 'https://intel.ingress.com',
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Safari/605.1.15',
+ 'Referer': 'https://intel.ingress.com/',
+}
+
+
+
+//getPlexts : {"minLatE6":55740916,"minLngE6":49151362,"maxLatE6":55752993,"maxLngE6":49221056,"minTimestampMs":-1,"maxTimestampMs":-1,"tab":"all","v":"93c872fd0763076709bed329e421c37f5fa45fa4"}
+//getPortalDetails : {"guid":"e9ac08ad60db42abb948970defb28548.16","v":"93c872fd0763076709bed329e421c37f5fa45fa4"}
+
+type Cache = {
+ portals: {
+ [tile: string]: string[]
+ },
+ links: {
+ [tile: string]: string[]
+ },
+ fields: {
+ [tile: string]: string[]
+ }
+}
+const cache: Cache = {
+ fields: {},
+ links: {},
+ portals: {},
+}
+const ZOOM_TO_LEVEL = [8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, 1]
+const TILES_PER_EDGE = [1, 1, 1, 40, 40, 80, 80, 320, 1000, 2000, 2000, 4000, 8000, 16000, 16000, 32000]
+const ZOOM_TO_LINK_LENGTH = [200000, 200000, 200000, 200000, 200000, 60000, 60000, 10000, 5000, 2500, 2500, 800, 300, 0, 0]
+
+export const getTilesToLoad = (region: Region, width: number): string[] => {
+ const zoom = getZoomByRegion(width, region)
+ const dataZoom = getDataZoomForMapZoom(zoom);
+ const tileParams = getMapZoomTileParameters(dataZoom)
+ const bbox = getBoundingBox(region)
+ const x1 = lngToTile(bbox.west, tileParams);
+ const x2 = lngToTile(bbox.east, tileParams);
+ const y1 = latToTile(bbox.north, tileParams);
+ const y2 = latToTile(bbox.south, tileParams);
+ let tilesToLoad: string[] = []
+ for (var y = y1; y <= y2; y++) {
+ for (var x = x1; x <= x2; x++) {
+ const tile_id = pointToTileId(x, y, tileParams);
+ tilesToLoad.push(tile_id)
+ }
+ }
+ return tilesToLoad
+}
+
+export const loadTiles = async (tilesToLoad: string[], params: RequestParams): Promise<LoadedResult> => {
+ const chunk = tilesToLoad.splice(0, 25)
+ const loadedData: LoadedResult = {
+ fields: {},
+ links: {},
+ portals: {},
+ portalsByTile: {},
+ fieldsByTile: {},
+ linksByTile: {},
+ failedTiles: tilesToLoad,
+ loaded: false,
+ }
+ await getEntities(chunk, params)
+ .then(loaded => {
+ const result = loaded.result
+ if (!result || !result['map']) {
+ return false
+ }
+ return Object.keys(result['map']).map(tile => {
+ if (result['map'][tile]['error'] || !result['map'][tile]['gameEntities']) {
+ loadedData.failedTiles.push(tile)
+ return true
+ }
+ if (!loadedData.portalsByTile[tile]) {
+ loadedData.portalsByTile[tile] = []
+ }
+ if (!loadedData.fieldsByTile[tile]) {
+ loadedData.fieldsByTile[tile] = []
+ }
+ if (!loadedData.linksByTile[tile]) {
+ loadedData.linksByTile[tile] = []
+ }
+ return result['map'][tile]['gameEntities'].map((e: any[]) => {
+ const guid = e[0]
+ const de = decodeEntity(e)
+ if (de instanceof Portal) {
+ if (loadedData.portalsByTile[tile].indexOf(guid) == -1) {
+ loadedData.portalsByTile[tile].push(guid)
+ }
+ loadedData.portals[guid] = de
+ } else if (de instanceof Field) {
+ if (loadedData.fieldsByTile[tile].indexOf(guid) == -1) {
+ loadedData.fieldsByTile[tile].push(guid)
+ }
+ loadedData.fields[guid] = de
+ } else if (de instanceof Link) {
+ if (loadedData.linksByTile[tile].indexOf(guid) == -1) {
+ loadedData.linksByTile[tile].push(guid)
+ }
+ loadedData.links[guid] = de
+ }
+ return true
+ })
+ })
+ }).catch(e => {
+ loadedData.failedTiles = [...loadedData.failedTiles, ...chunk]
+ })
+ return loadedData
+}
+
+const getBoundingBox = (region: Region): BoundingBox => {
+ return {
+ east: region.longitude + (region.longitudeDelta / 2),
+ north: region.latitude + (region.latitudeDelta / 2),
+ south: region.latitude - (region.latitudeDelta / 2),
+ west: region.longitude - (region.longitudeDelta / 2),
+ }
+}
+
+const lngToTile = (lng: any, tileParams: TileParameters) => {
+ return Math.floor((lng + 180) / 360 * tileParams.tilesPerEdge);
+}
+
+const latToTile = (lat: any, tileParams: TileParameters) => {
+ return Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) +
+ 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * tileParams.tilesPerEdge);
+}
+
+const tileToLng = (x: number, params: TileParameters): number => {
+ return x / params.tilesPerEdge * 360 - 180;
+}
+
+const tileToLat = (y: number, params: TileParameters): number => {
+ var n = Math.PI - 2 * Math.PI * y / params.tilesPerEdge;
+ return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
+}
+
+const pointToTileId = (x: number, y: number, params: TileParameters): string => {
+ //change to quadkey construction
+ //as of 2014-05-06: zoom_x_y_minlvl_maxlvl_maxhealth
+
+ return params.zoom + "_" + x + "_" + y + "_" + params.level + "_8_100";
+}
+
+const getZoomByRegion = (width: number, region: Region): number => {
+ return Math.ceil(Math.log2(360 * ((width / 256) / region.longitudeDelta))) + 1
+}
+
+const getDataZoomForMapZoom = (zoom: number): number => {
+ if (zoom > 21) {
+ zoom = 21;
+ }
+ var origTileParams = getMapZoomTileParameters(zoom);
+ while (zoom > 3) {
+ var newTileParams = getMapZoomTileParameters(zoom - 1);
+
+ if (newTileParams.tilesPerEdge != origTileParams.tilesPerEdge
+ || newTileParams.hasPortals != origTileParams.hasPortals
+ || newTileParams.level * (newTileParams.hasPortals ? 1 : 0) != origTileParams.level * (origTileParams.hasPortals ? 1 : 0) // multiply by 'hasPortals' bool - so comparison does not matter when no portals available
+ ) {
+ // switching to zoom-1 would result in a different detail level - so we abort changing things
+ break;
+ } else {
+ // changing to zoom = zoom-1 results in identical tile parameters - so we can safely step back
+ // with no increase in either server load or number of requests
+ zoom = zoom - 1;
+ }
+ }
+ return zoom;
+}
+
+const getMapZoomTileParameters = (zoom: number): TileParameters => {
+ var level = ZOOM_TO_LEVEL.slice(0, 15)[zoom] || 0;
+
+ if (level <= 7 && level >= 4) {
+ level = level + 1;
+ }
+
+ var maxTilesPerEdge = TILES_PER_EDGE[TILES_PER_EDGE.length - 1];
+
+ return {
+ level: level,
+ maxLevel: ZOOM_TO_LEVEL.slice(0, 15)[zoom] || 0, // for reference, for log purposes, etc
+ tilesPerEdge: TILES_PER_EDGE[zoom] || maxTilesPerEdge,
+ minLinkLength: ZOOM_TO_LINK_LENGTH[zoom] || 0,
+ hasPortals: zoom >= ZOOM_TO_LINK_LENGTH.length, // no portals returned at all when link length limits things
+ zoom: zoom // include the zoom level, for reference
+ };
+}
+
+type RequestParams = {
+ csrf: string, v: string
+}
+
+function timeout(ms, promise) {
+ return new Promise(function (resolve, reject) {
+ setTimeout(function () {
+ reject(new Error("timeout"))
+ }, ms)
+ return promise.then(resolve, reject)
+ })
+}
+
+const getEntities = (tileKeys: string[], params: RequestParams): Promise<GetEntitiesResponse> => {
+ const promise = fetch("https://intel.ingress.com/r/getEntities", {
+ "credentials": 'include',
+ "headers": {
+ ...baseHeaders,
+ 'X-CSRFToken': params.csrf,
+ },
+ "referrer": "https://intel.ingress.com/",
+ "body": JSON.stringify({ tileKeys, v: params.v }),
+ "method": "POST",
+ })
+ return timeout(10000, promise.then(r => {
+ if (r.status != 200) {
+ return { result: { map: {} } }
+ }
+ return r.json()
+ }))
+}
+
+export const getPortalDetails = (guid: string, params: RequestParams): Promise<GetPortalResponse> => {
+ const promise = fetch("https://intel.ingress.com/r/getPortalDetails", {
+ "credentials": 'include',
+ "headers": {
+ ...baseHeaders,
+ 'X-CSRFToken': params.csrf,
+ },
+ "referrer": "https://intel.ingress.com/",
+ "body": JSON.stringify({ guid, v: params.v }),
+ "method": "POST",
+ })
+ return timeout(10000, promise.then(r => {
+ if (r.status != 200) {
+ return { result: [] }
+ }
+ return r.json()
+ }))
+}