geojson#Point TypeScript Examples

The following examples show how to use geojson#Point. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ews-utils.ts    From prism-frontend with MIT License 6 votes vote down vote up
fetchEWSData = async (
  baseUrl: string,
  date: number,
): Promise<PointLayerData> => {
  const [locations, values] = await Promise.all([
    fetchEWSLocations(baseUrl),
    fetchEWSDataPointsByLocation(baseUrl, date),
  ]);

  const processedFeatures: PointData[] = locations.features.reduce(
    (pointDataArray, feature) => {
      const { properties, geometry } = feature;

      if (!properties) {
        return pointDataArray;
      }

      const locationValues: number[] = values
        .filter(v => v.location_id === properties.id)
        .map(v => v.value[1]);

      if (locationValues.length === 0) {
        return pointDataArray;
      }

      const mean =
        locationValues.reduce((acc, item) => acc + item, 0) /
        locationValues.length;
      const min = Math.min(...locationValues);
      const max = Math.max(...locationValues);

      const { coordinates } = geometry as Point;

      const pointData: PointData = {
        lon: coordinates[0],
        lat: coordinates[1],
        date,
        mean: parseFloat(mean.toFixed(2)),
        min,
        max,
        ...properties,
        status: getLevelStatus(
          max,
          properties.trigger_levels as EWSTriggerLevels,
        ),
      };

      return [...pointDataArray, pointData];
    },
    [] as PointData[],
  );

  return {
    features: GeoJSON.parse(processedFeatures, {
      Point: ['lat', 'lon'],
    }),
  };
}
Example #2
Source File: test.service.ts    From aqualink-app with MIT License 6 votes vote down vote up
private async loadMocks(connection: Connection) {
    await connection.getRepository(User).save(users);
    await connection.getRepository(Site).save(sites);
    await connection.getRepository(SiteSurveyPoint).save(surveyPoints);
    await connection.getRepository(SiteApplication).save(siteApplications);
    await connection.getRepository(Sources).save(sources);
    await connection.getRepository(TimeSeries).save(timeSeries);
    await connection.query('REFRESH MATERIALIZED VIEW latest_data');
    await connection.getRepository(Collection).save(collections);
    await connection.getRepository(DailyData).save(dailyData);
    await connection.getRepository(Survey).save(surveys);
    await connection.getRepository(SurveyMedia).save(surveyMedia);

    await Bluebird.map(sites, async (site) => {
      const [longitude, latitude] = (site.polygon as Point).coordinates;
      const historicalMonthlyMean = await getHistoricalMonthlyMeans(
        longitude,
        latitude,
      );

      return Bluebird.map(historicalMonthlyMean, (hmm) => {
        return connection.getRepository(HistoricalMonthlyMean).save({
          site,
          month: hmm.month,
          temperature: hmm.temperature,
        });
      });
    });
  }
Example #3
Source File: sofar-availability.ts    From aqualink-app with MIT License 6 votes vote down vote up
export function getSofarWaveModelAvailability(): FeatureCollection<Point> {
  if (!geojson) {
    geojson = {
      type: 'FeatureCollection',
      features: availabilityPoints.map((coordinate) => ({
        type: 'Feature',
        geometry: {
          type: 'Point',
          coordinates: coordinate,
        },
      })),
    };
  }
  return geojson;
}
Example #4
Source File: coordinates.ts    From aqualink-app with MIT License 6 votes vote down vote up
createPoint = (
  longitude: number,
  latitude: number,
  numberOfDecimals: number = 5,
): Point => {
  const precision = 10 ** numberOfDecimals;
  return {
    type: 'Point',
    coordinates: [
      Math.round((longitude + Number.EPSILON) * precision) / precision,
      Math.round((latitude + Number.EPSILON) * precision) / precision,
    ],
  };
}
Example #5
Source File: augment-site-data.ts    From aqualink-app with MIT License 6 votes vote down vote up
async function getAugmentedData(
  site: Site,
  regionRepository: Repository<Region>,
) {
  const [longitude, latitude] = (site.polygon as Point).coordinates;

  const region =
    site.region || (await getRegion(longitude, latitude, regionRepository));

  const MMM = await getMMM(longitude, latitude);
  if (MMM === null) {
    console.warn(
      `Max Monthly Mean appears to be null for Site ${site.name} at (lat, lon): (${latitude}, ${longitude}) `,
    );
  }

  const timezones = geoTz(latitude, longitude);

  return omitBy(
    {
      region,
      timezone: timezones.length > 0 ? timezones[0] : null,
      maxMonthlyMean: MMM,
    },
    isNil,
  );
}
Example #6
Source File: RegionLabelLayer.tsx    From Teyvat.moe with GNU General Public License v3.0 6 votes vote down vote up
_RegionLabelLayer: FunctionComponent<RegionLabelLayerProps> = ({ displayed, zoomLevel }) => {
  const classes = useStyles();

  const map = useMap();
  const layerReference = useRef<GeoJSONLeaflet | null>(null);

  useEffect(() => {
    if (layerReference.current != null) {
      if (displayed) {
        layerReference.current.addTo(map);
      } else {
        layerReference.current.removeFrom(map);
      }
    }
  }, [map, displayed]);

  const pointToLayer = (featureData: Feature<Point, any>, latLng: LatLng) => {
    const html = renderToString(<RegionLabel featureData={featureData} zoomLevel={zoomLevel} />);

    return LeafletMarker([latLng.lng, latLng.lat], {
      interactive: false, // Allow clicks to pass through.
      icon: LeafletDivIcon({
        html,
        className: classes.regionLabelMarker,
      }),
      zIndexOffset: -900,
    });
  };

  return (
    <GeoJSON
      ref={layerReference}
      key={zoomLevel}
      pointToLayer={pointToLayer}
      data={RegionLabelData as GeoJsonObject}
    />
  );
}
Example #7
Source File: sensors.service.ts    From aqualink-app with MIT License 5 votes vote down vote up
async findSensors(): Promise<
    (Site & { sensorPosition: GeoJSON; sensorType: SensorType })[]
  > {
    const sites = await this.siteRepository.find({
      where: { sensorId: Not(IsNull()) },
    });

    // Get spotter data and add site id to distinguish them
    const spotterData = await Bluebird.map(
      sites,
      (site) => {
        if (site.sensorId === null) {
          console.warn(`Spotter for site ${site.id} appears null.`);
        }
        return getSpotterData(site.sensorId!).then((data) => {
          return {
            id: site.id,
            ...data,
          };
        });
      },
      { concurrency: 10 },
    );

    // Group spotter data by site id for easier search
    const siteIdToSpotterData: Record<number, SpotterData & { id: number }> =
      keyBy(spotterData, (o) => o.id);

    // Construct final response
    return sites.map((site) => {
      const data = siteIdToSpotterData[site.id];
      const longitude = getLatestData(data.longitude)?.value;
      const latitude = getLatestData(data.latitude)?.value;
      const sitePosition = site.polygon as Point;

      // If no longitude or latitude is provided by the spotter fallback to the site coordinates
      return {
        ...site,
        applied: site.applied,
        sensorPosition: createPoint(
          longitude || sitePosition.coordinates[0],
          latitude || sitePosition.coordinates[1],
        ),
        sensorType: SensorType.SofarSpotter,
      };
    });
  }
Example #8
Source File: augment-site-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
async function augmentSites(connection: Connection) {
  const siteRepository = connection.getRepository(Site);
  const regionRepository = connection.getRepository(Region);
  const HistoricalMonthlyMeanRepository = connection.getRepository(
    HistoricalMonthlyMean,
  );
  const allSites = await siteRepository.find();

  const start = new Date();
  console.log(`Augmenting ${allSites.length} sites...`);
  await Bluebird.map(
    allSites,
    async (site) => {
      const augmentedData = await getAugmentedData(site, regionRepository);
      await siteRepository.update(site.id, augmentedData);
      // Add HistoricalMonthlyMeans
      const [longitude, latitude] = (site.polygon as Point).coordinates;
      const HistoricalMonthlyMeans = await getHistoricalMonthlyMeans(
        longitude,
        latitude,
      );
      await Promise.all(
        HistoricalMonthlyMeans.map(async ({ month, temperature }) => {
          try {
            await (temperature &&
              HistoricalMonthlyMeanRepository.insert({
                site,
                month,
                temperature,
              }));
          } catch {
            console.warn(
              `Monthly max values not imported for ${site.id} - the data was likely there already.`,
            );
          }
        }),
      );
    },
    { concurrency: 1 },
  );
  console.log(
    `Augmented ${allSites.length} sites in ${
      (new Date().valueOf() - start.valueOf()) / 1000
    } seconds`,
  );
}
Example #9
Source File: liveData.ts    From aqualink-app with MIT License 5 votes vote down vote up
getLiveData = async (
  site: Site,
  isDeployed: boolean,
): Promise<SofarLiveData> => {
  console.time(`getLiveData for site ${site.id}`);
  const { polygon, sensorId, maxMonthlyMean } = site;
  // TODO - Accept Polygon option
  const [longitude, latitude] = (polygon as Point).coordinates;

  const now = new Date();

  const [spotterRawData, degreeHeatingDays, satelliteTemperature] =
    await Promise.all([
      sensorId && isDeployed ? getSpotterData(sensorId) : undefined,
      getDegreeHeatingDays(latitude, longitude, now, maxMonthlyMean),
      getSofarHindcastData(
        SofarModels.NOAACoralReefWatch,
        sofarVariableIDs[SofarModels.NOAACoralReefWatch]
          .analysedSeaSurfaceTemperature,
        latitude,
        longitude,
        now,
        96,
      ),
    ]);

  const spotterData = spotterRawData
    ? {
        topTemperature: getLatestData(spotterRawData.topTemperature),
        bottomTemperature: getLatestData(spotterRawData.bottomTemperature),
        longitude:
          spotterRawData.longitude && getLatestData(spotterRawData.longitude),
        latitude:
          spotterRawData.latitude && getLatestData(spotterRawData.latitude),
      }
    : {};

  const filteredValues = omitBy(
    {
      degreeHeatingDays,
      satelliteTemperature:
        satelliteTemperature && getLatestData(satelliteTemperature),
      // Override all possible values with spotter data.
      ...spotterData,
    },
    (data) => isNil(data?.value) || data?.value === 9999,
  );

  const dailyAlertLevel = calculateAlertLevel(
    maxMonthlyMean,
    filteredValues?.satelliteTemperature?.value,
    degreeHeatingDays?.value,
  );

  console.timeEnd(`getLiveData for site ${site.id}`);

  return {
    site: { id: site.id },
    ...filteredValues,
    ...(spotterData.longitude &&
      spotterData.latitude && {
        spotterPosition: {
          longitude: spotterData.longitude,
          latitude: spotterData.latitude,
        },
      }),
    dailyAlertLevel,
  };
}
Example #10
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
getSiteRecords = async (
  dataAsJson: Coords[],
  siteIds: number[],
  regionRepository: Repository<Region>,
) => {
  // Group by site
  const recordsGroupedBySite = groupBy(dataAsJson, 'site');

  // Extract site entities and calculate position of site by averaging all each surveyPoints positions
  const sites = await Promise.all(
    siteIds.map((siteId) => {
      // Filter out NaN values
      const filteredSiteCoords = recordsGroupedBySite[siteId].filter(
        (record) => !isNaN(record.lat) && !isNaN(record.long),
      );

      const siteRecord = filteredSiteCoords.reduce(
        (previous, record) => {
          return {
            ...previous,
            lat: previous.lat + record.lat,
            long: previous.long + record.long,
          };
        },
        { site: siteId, colony: 0, lat: 0, long: 0 },
      );

      // Calculate site position
      const point: Point = createPoint(
        siteRecord.long / filteredSiteCoords.length,
        siteRecord.lat / filteredSiteCoords.length,
      );

      // Augment site information
      const [longitude, latitude] = point.coordinates;
      const timezones = getTimezones(latitude, longitude) as string[];

      return Promise.all([
        getRegion(longitude, latitude, regionRepository),
        getMMM(longitude, latitude),
      ]).then(([region, maxMonthlyMean]) => ({
        name: SITE_PREFIX + siteId,
        polygon: point,
        region,
        maxMonthlyMean,
        display: false,
        timezone: timezones[0],
        status: SiteStatus.Approved,
      }));
    }),
  );

  return { recordsGroupedBySite, sites };
}
Example #11
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
createSites = async (
  sites: Partial<Site>[],
  user: User,
  siteRepository: Repository<Site>,
  userRepository: Repository<User>,
  historicalMonthlyMeanRepository: Repository<HistoricalMonthlyMean>,
) => {
  logger.log('Saving sites');
  const siteEntities = await Promise.all(
    sites.map((site) =>
      siteRepository
        .save(site)
        .catch(handleEntityDuplicate(siteRepository, siteQuery, site.polygon)),
    ),
  );

  logger.log(`Saving monthly max data`);
  await Bluebird.map(
    siteEntities,
    (site) => {
      const point: Point = site.polygon as Point;
      const [longitude, latitude] = point.coordinates;

      return Promise.all([
        getHistoricalMonthlyMeans(longitude, latitude),
        historicalMonthlyMeanRepository.findOne({ where: { site } }),
      ]).then(([historicalMonthlyMean, found]) => {
        if (found || !historicalMonthlyMean) {
          logger.warn(`Site ${site.id} has already monthly max data`);
          return null;
        }

        return historicalMonthlyMean.map(({ month, temperature }) => {
          return (
            temperature &&
            historicalMonthlyMeanRepository.save({ site, month, temperature })
          );
        });
      });
    },
    { concurrency: 4 },
  );

  // Create reverse map (db.site.id => csv.site_id)
  const dbIdToCSVId: Record<number, number> = Object.fromEntries(
    siteEntities.map((site) => {
      if (!site.name) {
        throw new InternalServerErrorException('Site name was not defined');
      }

      const siteId = parseInt(site.name.replace(SITE_PREFIX, ''), 10);
      return [site.id, siteId];
    }),
  );

  // Update administered sites relationship
  await userRepository.save({
    id: user.id,
    administeredSites: user.administeredSites.concat(siteEntities),
  });

  return { siteEntities, dbIdToCSVId };
}
Example #12
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
createSurveyPoints = async (
  siteEntities: Site[],
  dbIdToCSVId: Record<number, number>,
  recordsGroupedBySite: Dictionary<Coords[]>,
  rootPath: string,
  poiRepository: Repository<SiteSurveyPoint>,
) => {
  // Create site points of interest entities for each imported site
  // Final result needs to be flattened since the resulting array is grouped by site
  const surveyPoints = siteEntities
    .map((site) => {
      const currentSiteId = dbIdToCSVId[site.id];
      const siteFolder = FOLDER_PREFIX + currentSiteId;
      return recordsGroupedBySite[currentSiteId]
        .filter((record) => {
          const colonyId = record.colony.toString().padStart(3, '0');
          const colonyFolder = COLONY_FOLDER_PREFIX + colonyId;
          const colonyFolderPath = path.join(
            rootPath,
            siteFolder,
            colonyFolder,
          );

          return fs.existsSync(colonyFolderPath);
        })
        .map((record) => {
          const point: Point | undefined =
            !isNaN(record.long) && !isNaN(record.lat)
              ? createPoint(record.long, record.lat)
              : undefined;

          return {
            name: COLONY_PREFIX + record.colony,
            site,
            polygon: point,
          };
        });
    })
    .flat();

  logger.log('Saving site points of interest');
  const poiEntities = await Promise.all(
    surveyPoints.map((poi) =>
      poiRepository
        .save(poi)
        .catch(handleEntityDuplicate(poiRepository, poiQuery, poi.polygon)),
    ),
  );

  return poiEntities;
}
Example #13
Source File: geospatialService.ts    From react-flight-tracker with MIT License 5 votes vote down vote up
private calculatePath = (stateVectors: IStateVectorData) => {

    const features: Array<Feature<Point, GeoJsonProperties>> = [];

    for (var stateVector of stateVectors.states) {

      // Setup last position time in ms
      var lastPositionTime = this.pathPredictionCounter

      // Setup altitude in m
      var altitude = stateVector.geo_altitude;
      if ((altitude === null) || (altitude < 0))
        altitude = stateVector.baro_altitude;
      if ((altitude === null) || (altitude < 0))
        altitude = 0;

      // Setup vertical rate
      var verticalRate = stateVector.vertical_rate ? stateVector.vertical_rate : 0.0;
      if (verticalRate < 0)
        verticalRate *= -1;

      const origin: Array<number> = [stateVector.longitude ? stateVector.longitude : 0, stateVector.latitude ? stateVector.latitude : 0]
      const velocity = stateVector.velocity ? stateVector.velocity : 0;

      var distance = (velocity * lastPositionTime) / 1000;

      // Try to adjust the distance to the vertical rate
      if (verticalRate !== 0)
        distance = distance - (verticalRate * (lastPositionTime / 1000));

      // Try to adjust the distance to the altitude
      if (altitude > 0)
        distance = (distance * earthRadius) / (earthRadius + altitude);

      const bearing = stateVector.true_track ? stateVector.true_track : 0;

      // Calculate the destination
      const feature = destination(
        origin,
        distance,
        bearing,
        {
          units: "meters"
        }
      );

      // Adding the ICAO24 prop to the feature so that a corresponding assignment is possible later
      var properties: GeoJsonProperties = {
        ['icao24']: stateVector.icao24
      };
      feature.properties = properties;

      // Push the feature to the collection
      features.push(feature);
    }

    // Increase counter time
    this.pathPredictionCounter += this.pathPredictionInterval;

    // Execute callbacks
    Object.entries(this.pathPredictionUpdatedSubscriberDictionary).forEach(([key, value], index) => value(features))
  };
Example #14
Source File: sst-backfill.ts    From aqualink-app with MIT License 4 votes vote down vote up
async function main() {
  const connection = await createConnection(dbConfig);
  const siteRepository = connection.getRepository(Site);
  const dailyDataRepository = connection.getRepository(DailyData);
  const sourcesRepository = connection.getRepository(Sources);
  const timeSeriesRepository = connection.getRepository(TimeSeries);
  const selectedSites = await siteRepository.find({
    where:
      sitesToProcess.length > 0
        ? {
            id: In(sitesToProcess),
          }
        : {},
  });

  const dailyDataEntities = selectedSites.reduce(
    (entities: DailyData[], site) => {
      console.log(`Processing site ${site.name || site.id}...`);
      const allYearEntities = yearsArray.reduce(
        (yearEntities: DailyData[], year) => {
          console.log(`Processing year ${year}...`);
          const [longitude, latitude] = (site.polygon as Point).coordinates;
          const data = getNOAAData(year, longitude, latitude);
          const yearEntitiesForSite = data.map(
            ({ date, satelliteTemperature }) =>
              ({
                site: { id: site.id },
                date,
                satelliteTemperature,
              } as DailyData),
          );
          return yearEntities.concat(yearEntitiesForSite);
        },
        [],
      );
      return entities.concat(allYearEntities);
    },
    [],
  );

  const sources = await Promise.all(
    selectedSites.map((site) => {
      return getNOAASource(site, sourcesRepository);
    }),
  );

  const siteToSource: Record<number, Sources> = keyBy(
    sources,
    (source) => source.site.id,
  );

  await Bluebird.map(dailyDataEntities, async (entity) => {
    try {
      await dailyDataRepository.save(entity);
    } catch (err) {
      if (err.constraint === 'no_duplicated_date') {
        console.debug(
          `Data already exists for this date ${entity.date.toDateString()}`,
        );
      } else {
        console.error(err);
      }
    }

    if (!entity.satelliteTemperature) {
      return;
    }

    await insertSiteDataToTimeSeries(
      [
        {
          value: entity.satelliteTemperature,
          timestamp: entity.date.toISOString(),
        },
      ],
      Metric.SATELLITE_TEMPERATURE,
      siteToSource[entity.site.id],
      timeSeriesRepository,
    );
  });

  // Update materialized view
  console.log('Refreshing materialized view latest_data');
  await connection.query('REFRESH MATERIALIZED VIEW latest_data');

  connection.close();
  process.exit(0);
}
Example #15
Source File: hindcast-wind-wave.ts    From aqualink-app with MIT License 4 votes vote down vote up
addWindWaveData = async (
  siteIds: number[],
  repositories: Repositories,
) => {
  logger.log('Fetching sites');
  // Fetch all sites
  const sites = await getSites(siteIds, false, repositories.siteRepository);

  const date = new Date();
  const yesterdayDate = new Date(date);
  yesterdayDate.setDate(date.getDate() - 1);
  const today = date.toISOString();
  const yesterday = yesterdayDate.toISOString();

  logger.log('Saving wind & wave forecast data');
  await Bluebird.map(
    sites,
    async (site) => {
      const { polygon } = site;

      const [longitude, latitude] = getSofarNearestAvailablePoint(
        polygon as Point,
      );

      logger.log(
        `Saving wind & wave forecast data for ${site.id} at ${latitude} - ${longitude}`,
      );

      const hindcastOptions = [
        [
          SofarModels.SofarOperationalWaveModel,
          sofarVariableIDs[SofarModels.SofarOperationalWaveModel]
            .significantWaveHeight,
        ],
        [
          SofarModels.SofarOperationalWaveModel,
          sofarVariableIDs[SofarModels.SofarOperationalWaveModel].meanDirection,
        ],
        [
          SofarModels.SofarOperationalWaveModel,
          sofarVariableIDs[SofarModels.SofarOperationalWaveModel].meanPeriod,
        ],
        [
          SofarModels.GFS,
          sofarVariableIDs[SofarModels.GFS].windVelocity10MeterEastward,
        ],
        [
          SofarModels.GFS,
          sofarVariableIDs[SofarModels.GFS].windVelocity10MeterNorthward,
        ],
      ];

      const response = await Promise.all(
        hindcastOptions.map(([sofarModel, sofarVariableId]) => {
          return sofarHindcast(
            sofarModel,
            sofarVariableId,
            latitude,
            longitude,
            yesterday,
            today,
          );
        }),
      );

      const [
        significantWaveHeight,
        waveMeanDirection,
        waveMeanPeriod,
        windVelocity10MeterEastward,
        windVelocity10MeterNorthward,
      ] = response.map((x) => {
        if (!x || x.values.length < 1) return undefined;
        return x.values[x.values.length - 1]; // latest available forecast in the past
      });

      // Calculate wind speed and direction from velocity
      // TODO: treat undefined better
      const windNorthwardVelocity = windVelocity10MeterNorthward?.value;
      const windEastwardVelocity = windVelocity10MeterEastward?.value;
      const sameTimestamps =
        windVelocity10MeterEastward?.timestamp ===
        windVelocity10MeterNorthward?.timestamp;
      const windSpeed: ValueWithTimestamp | undefined =
        windNorthwardVelocity && windEastwardVelocity && sameTimestamps
          ? {
              timestamp: windVelocity10MeterNorthward?.timestamp,
              value: getWindSpeed(windEastwardVelocity, windNorthwardVelocity),
            }
          : undefined;
      const windDirection: ValueWithTimestamp | undefined =
        windNorthwardVelocity && windEastwardVelocity && sameTimestamps
          ? {
              timestamp: windVelocity10MeterNorthward?.timestamp,
              value: getWindDirection(
                windEastwardVelocity,
                windNorthwardVelocity,
              ),
            }
          : undefined;

      const forecastData = {
        significantWaveHeight,
        waveMeanDirection,
        waveMeanPeriod,
        windSpeed,
        windDirection,
      };

      // Save wind wave data to forecast_data
      await Promise.all(
        // eslint-disable-next-line array-callback-return, consistent-return
        dataLabels.map(([dataLabel, metric]) => {
          const sofarValue = forecastData[dataLabel] as ValueWithTimestamp;
          if (!isNil(sofarValue?.value) && !Number.isNaN(sofarValue?.value)) {
            return repositories.hindcastRepository
              .createQueryBuilder('forecast_data')
              .insert()
              .values([
                {
                  site,
                  timestamp: moment(sofarValue.timestamp)
                    .startOf('minute')
                    .toDate(),
                  metric,
                  value: sofarValue.value,
                  updatedAt: today,
                },
              ])
              .onConflict(
                `ON CONSTRAINT "one_row_per_site_per_metric" DO UPDATE SET "timestamp" = excluded."timestamp", "updated_at" = excluded."updated_at", "value" = excluded."value"`,
              )
              .execute();
          }
        }),
      );
    },
    { concurrency: 4 },
  );
  logger.log('Completed updating hindcast data');
}
Example #16
Source File: sst-time-series.ts    From aqualink-app with MIT License 4 votes vote down vote up
updateSST = async (
  siteIds: number[],
  days: number,
  connection: Connection,
  repositories: Repositories,
) => {
  const { siteRepository, timeSeriesRepository, sourceRepository } =
    repositories;

  logger.log('Fetching sites');
  // Fetch sites entities
  const sites = await getSites(siteIds, siteRepository);

  // Fetch sources
  const sources = await Promise.all(
    sites.map((site) => {
      return getNOAASource(site, sourceRepository);
    }),
  );

  await Bluebird.map(
    sources,
    async (source) => {
      const { site } = source;
      const point = site.polygon as Point;
      // Extract site coordinates
      const [longitude, latitude] = point.coordinates;

      logger.log(`Back-filling site with id ${site.id}.`);

      const data = await Bluebird.map(
        times(days),
        // A non-async function is used on purpose.
        // We need for as many http request to be performed simultaneously without one blocking the other
        // This way we get a much greater speed up due to the concurrency.
        (i) => {
          const endDate =
            i === 0
              ? moment().format()
              : moment().subtract(i, 'd').endOf('day').format();
          const startDate = moment().subtract(i, 'd').startOf('day').format();

          // use Promise/then to increase concurrency since await halts the event loop
          return Promise.all([
            // Fetch satellite surface temperature data
            sofarHindcast(
              SofarModels.NOAACoralReefWatch,
              sofarVariableIDs[SofarModels.NOAACoralReefWatch]
                .analysedSeaSurfaceTemperature,
              latitude,
              longitude,
              startDate,
              endDate,
            ),
            // Fetch degree heating weeks data
            sofarHindcast(
              SofarModels.NOAACoralReefWatch,
              sofarVariableIDs[SofarModels.NOAACoralReefWatch]
                .degreeHeatingWeek,
              latitude,
              longitude,
              startDate,
              endDate,
            ),
          ]).then(([SofarSSTRaw, sofarDegreeHeatingWeekRaw]) => {
            // Filter out null values
            const sstFiltered = filterSofarResponse(SofarSSTRaw);
            const dhwFiltered = filterSofarResponse(sofarDegreeHeatingWeekRaw);
            // Get latest dhw
            const latestDhw = getLatestData(dhwFiltered);
            // Get alert level
            const alertLevel = calculateAlertLevel(
              site.maxMonthlyMean,
              getLatestData(sstFiltered)?.value,
              // Calculate degree heating days
              latestDhw && latestDhw.value * 7,
            );

            // Calculate the sstAnomaly
            const sstAnomaly = sstFiltered
              .map((sst) => ({
                value: getSstAnomaly(site.historicalMonthlyMean, sst),
                timestamp: sst.timestamp,
              }))
              // Filter out null values
              .filter((sstAnomalyValue) => {
                return !isNil(sstAnomalyValue.value);
              }) as ValueWithTimestamp[];

            // return calculated metrics (sst, dhw, sstAnomaly alert)
            return {
              sst: sstFiltered,
              dhw: dhwFiltered,
              sstAnomaly,
              alert:
                alertLevel !== undefined
                  ? [
                      {
                        value: alertLevel,
                        timestamp: moment().subtract(i, 'd').hour(12).format(),
                      },
                    ]
                  : [],
            };
          });
        },
        { concurrency: 100 },
      );

      return Bluebird.map(
        data,
        // Save data on time_series table
        ({ sst, dhw, alert, sstAnomaly }) =>
          Promise.all([
            insertSiteDataToTimeSeries(
              sst,
              Metric.SATELLITE_TEMPERATURE,
              source,
              timeSeriesRepository,
            ),
            insertSiteDataToTimeSeries(
              dhw,
              Metric.DHW,
              source,
              timeSeriesRepository,
            ),
            insertSiteDataToTimeSeries(
              alert,
              Metric.ALERT,
              source,
              timeSeriesRepository,
            ),
            insertSiteDataToTimeSeries(
              sstAnomaly,
              Metric.SST_ANOMALY,
              source,
              timeSeriesRepository,
            ),
          ]),
        { concurrency: 100 },
      );
    },
    // Speed up if this is just a daily update
    // Concurrency should remain low, otherwise it will overwhelm the sofar api server
    { concurrency: days <= 5 ? 10 : 1 },
  );

  logger.log('Back-filling weekly alert level');
  // We calculate weekly alert separately because it depends on values of alert levels across 7 days
  await Bluebird.map(
    times(days),
    async (i) => {
      const endDate =
        i === 0
          ? moment().format()
          : moment().subtract(i, 'd').endOf('day').format();

      logger.log(`Back-filling weekly alert for ${endDate}`);
      // Calculate max alert by fetching the max alert in the last 7 days
      // As timestamp it is selected the latest available timestamp
      const maxAlert = await repositories.timeSeriesRepository
        .createQueryBuilder('time_series')
        .select('MAX(value)', 'value')
        .addSelect('source_id', 'source')
        .addSelect('MAX(timestamp)', 'timestamp')
        .where('timestamp <= :endDate::timestamp', { endDate })
        .andWhere("timestamp >= :endDate::timestamp - INTERVAL '7 days'", {
          endDate,
        })
        .andWhere('metric = :alertMetric', { alertMetric: Metric.ALERT })
        .andWhere('source_id IN (:...sourceIds)', {
          sourceIds: sources.map((source) => source.id),
        })
        .groupBy('source_id')
        .getRawMany();

      await repositories.timeSeriesRepository
        .createQueryBuilder('time_series')
        .insert()
        .values(
          maxAlert.map((data) => ({
            ...data,
            metric: Metric.WEEKLY_ALERT,
          })),
        )
        .onConflict('ON CONSTRAINT "no_duplicate_data" DO NOTHING')
        .execute();
    },
    // Concurrency is set to 1 to avoid read and writing the table time_series at the same time
    { concurrency: 1 },
  );

  // Update materialized view
  logger.log('Refreshing materialized view latest_data');
  connection.query('REFRESH MATERIALIZED VIEW latest_data');
}
Example #17
Source File: dailyData.ts    From aqualink-app with MIT License 4 votes vote down vote up
export async function getDailyData(
  site: Site,
  endOfDate: Date,
  excludedDates: ExclusionDates[],
): Promise<SofarDailyData> {
  const { polygon, sensorId, maxMonthlyMean } = site;
  // TODO - Accept Polygon option
  const [longitude, latitude] = (polygon as Point).coordinates;

  const [
    spotterRawData,
    degreeHeatingDays,
    satelliteTemperatureData,
    significantWaveHeightsRaw,
    waveMeanDirectionRaw,
    waveMeanPeriodRaw,
    windVelocity10MeterEastward,
    windVelocity10MeterNorthward,
  ] = await Promise.all([
    sensorId ? getSpotterData(sensorId, endOfDate) : DEFAULT_SPOTTER_DATA_VALUE,
    // Calculate Degree Heating Days
    // Calculating Degree Heating Days requires exactly 84 days of data.
    getDegreeHeatingDays(latitude, longitude, endOfDate, maxMonthlyMean),
    getSofarHindcastData(
      SofarModels.NOAACoralReefWatch,
      sofarVariableIDs[SofarModels.NOAACoralReefWatch]
        .analysedSeaSurfaceTemperature,
      latitude,
      longitude,
      endOfDate,
      96,
    ),
    getSofarHindcastData(
      SofarModels.SofarOperationalWaveModel,
      sofarVariableIDs[SofarModels.SofarOperationalWaveModel]
        .significantWaveHeight,
      latitude,
      longitude,
      endOfDate,
    ).then((data) => data.map(({ value }) => value)),
    getSofarHindcastData(
      SofarModels.SofarOperationalWaveModel,
      sofarVariableIDs[SofarModels.SofarOperationalWaveModel].meanDirection,
      latitude,
      longitude,
      endOfDate,
    ).then((data) => data.map(({ value }) => value)),
    getSofarHindcastData(
      SofarModels.SofarOperationalWaveModel,
      sofarVariableIDs[SofarModels.SofarOperationalWaveModel].meanPeriod,
      latitude,
      longitude,
      endOfDate,
    ).then((data) => data.map(({ value }) => value)),
    // Get NOAA GFS wind data
    getSofarHindcastData(
      SofarModels.GFS,
      sofarVariableIDs[SofarModels.GFS].windVelocity10MeterEastward,
      latitude,
      longitude,
      endOfDate,
    ).then((data) => data.map(({ value }) => value)),
    getSofarHindcastData(
      SofarModels.GFS,
      sofarVariableIDs[SofarModels.GFS].windVelocity10MeterNorthward,
      latitude,
      longitude,
      endOfDate,
    ).then((data) => data.map(({ value }) => value)),
  ]);

  const spotterData = spotterRawData
    ? mapValues(spotterRawData, (v) =>
        extractSofarValues(filterMetricDataByDate(excludedDates, v)),
      )
    : {
        topTemperature: [],
        bottomTemperature: [],
        significantWaveHeight: [],
        waveMeanPeriod: [],
        waveMeanDirection: [],
        windSpeed: [],
        windDirection: [],
      };

  const minBottomTemperature = getMin(spotterData.bottomTemperature);
  const maxBottomTemperature = getMax(spotterData.bottomTemperature);
  const avgBottomTemperature = getAverage(spotterData.bottomTemperature);

  const topTemperature = getAverage(spotterData.topTemperature);

  // Get satelliteTemperature
  const latestSatelliteTemperature =
    satelliteTemperatureData && getLatestData(satelliteTemperatureData);
  const satelliteTemperature =
    latestSatelliteTemperature && latestSatelliteTemperature.value;

  // Get waves data if unavailable through a spotter
  const significantWaveHeights =
    spotterData.significantWaveHeight.length > 0
      ? spotterData.significantWaveHeight
      : significantWaveHeightsRaw;

  const minWaveHeight =
    significantWaveHeights && getMin(significantWaveHeights);
  const maxWaveHeight =
    significantWaveHeights && getMax(significantWaveHeights);
  const avgWaveHeight =
    significantWaveHeights && getAverage(significantWaveHeights);

  const meanDirectionWaves =
    spotterData.waveMeanDirection.length > 0
      ? spotterData.waveMeanDirection
      : waveMeanDirectionRaw;

  const waveMeanDirection =
    meanDirectionWaves && getAverage(meanDirectionWaves, true);

  const meanPeriodWaves =
    spotterData.waveMeanPeriod.length > 0
      ? spotterData.waveMeanPeriod
      : waveMeanPeriodRaw;

  const waveMeanPeriod = meanPeriodWaves && getAverage(meanPeriodWaves, true);

  // Make sure that windVelocity10MeterEastward and windVelocity10MeterNorthward have the same length.
  const modelWindCheck =
    windVelocity10MeterEastward.length === windVelocity10MeterNorthward.length;

  const windSpeedsRaw = modelWindCheck
    ? windVelocity10MeterEastward.map((eastValue, index) =>
        getWindSpeed(eastValue, windVelocity10MeterNorthward[index]),
      )
    : [];
  const windDirectionsRaw = modelWindCheck
    ? windVelocity10MeterEastward.map((eastValue, index) =>
        getWindDirection(eastValue, windVelocity10MeterNorthward[index]),
      )
    : [];

  // Get wind data if unavailable through a spotter
  const windSpeeds = isEmpty(spotterData.windSpeed)
    ? windSpeedsRaw
    : spotterData.windSpeed;
  const windDirections = isEmpty(spotterData.windDirection)
    ? windDirectionsRaw
    : spotterData.windDirection;

  const minWindSpeed = windSpeeds && getMin(windSpeeds);
  const maxWindSpeed = windSpeeds && getMax(windSpeeds);
  const avgWindSpeed = windSpeeds && getAverage(windSpeeds);

  const windDirection = windDirections && getAverage(windDirections, true);

  const dailyAlertLevel = calculateAlertLevel(
    maxMonthlyMean,
    satelliteTemperature,
    degreeHeatingDays?.value,
  );

  return {
    site: { id: site.id },
    date: endOfDate,
    dailyAlertLevel,
    minBottomTemperature,
    maxBottomTemperature,
    avgBottomTemperature,
    topTemperature,
    satelliteTemperature,
    degreeHeatingDays: degreeHeatingDays?.value,
    minWaveHeight,
    maxWaveHeight,
    avgWaveHeight,
    waveMeanDirection,
    waveMeanPeriod,
    minWindSpeed,
    maxWindSpeed,
    avgWindSpeed,
    windDirection,
  };
}
Example #18
Source File: AircraftLayer.tsx    From react-flight-tracker with MIT License 4 votes vote down vote up
AircraftLayer: React.FC<Props> = (props) => {

  // Fields
  const contextName: string = 'AircraftLayer'

  // External hooks
  const styleTheme = useTheme();

  // States
  const [featureCollection, setFeatureCollection] = useState<FeatureCollection | undefined>(undefined);
  const [pathPredictions, setPathPredictions] = useState<Array<Feature<Point, GeoJsonProperties>>>([]);

  // Contexts
  const systemContext = useContext(SystemContext)
  const geospatialService = systemContext.getService<IGeospatialService>('GeospatialService');

  // Refs
  const pathPredictionSubscriptionRef = useRef<string>('');

  // Effects
  useEffect(() => {

    // Mount
    if (geospatialService) {

      // Get a register key for the subscription and save it as reference
      const registerKey = geospatialService.onPathPredictionUpdated(contextName, handlePathPredictionUpdated);
      pathPredictionSubscriptionRef.current = registerKey;
    }

    // Unmount
    return () => {

      if (geospatialService) {

        geospatialService.stopPathPrediction();

        // Get the register key from the reference to unsubscribe
        const registerKey = pathPredictionSubscriptionRef.current;
        geospatialService.offPathPredictionUpdated(registerKey);
      }
    }
  }, []);
  useEffect(() => {

    createFeatureCollection(pathPredictions).then((featureCollection) => {

      setFeatureCollection(featureCollection);

      if (!geospatialService)
        return;

      if (props.stateVectors.states.length > 1000) {
        geospatialService.stopPathPrediction();
      }
      else {
        geospatialService.restartPathPrediction(props.stateVectors);
      }
    })



  }, [props.stateVectors]);
  useEffect(() => {

    createFeatureCollection(pathPredictions).then((featureCollection) => {

      setFeatureCollection(featureCollection);
    })

  }, [pathPredictions]);

  const handlePathPredictionUpdated = (destinations: Array<Feature<Point, GeoJsonProperties>>) => {
    setPathPredictions(destinations);
  };

  const createFeatureCollection = (pathPredictions: Array<Feature<Point, GeoJsonProperties>>) => {

    return new Promise<FeatureCollection>((res, rej) => {

      var featureCollection: FeatureCollection = {
        type: 'FeatureCollection',
        features: []
      };

      for (var stateVector of props.stateVectors.states) {

        if (!stateVector.latitude) {
          continue;
        }

        if (!stateVector.longitude) {
          continue;
        }

        // Get the index
        const index = props.stateVectors.states.indexOf(stateVector);

        // Check for selection
        var isSelected = false;
        if (props.selectedAircraft)
          isSelected = stateVector.icao24 === props.selectedAircraft.icao24;

        // Get callsign
        const callsign = stateVector.callsign ? stateVector.callsign : stateVector.icao24;

        // Get altitude
        var altitude = stateVector.geo_altitude;
        if ((altitude === null) || (altitude < 0))
          altitude = stateVector.baro_altitude;
        if ((altitude === null) || (altitude < 0))
          altitude = 0;

        // Get velocity in km/h
        const velocity = stateVector.velocity ? (stateVector.velocity * 3.6) : -1;

        // Get true track
        const trueTrack = stateVector.true_track ? stateVector.true_track : 0.0;

        // Get vertical rate
        const verticalRate = stateVector.vertical_rate ? stateVector.vertical_rate : 0.0;

        // Get is on ground
        const isOnGround = stateVector.on_ground;

        // Claculate color
        var color = getColor(altitude);
        if (isOnGround)
          color = styleTheme.palette.text.secondary;
        if (isSelected)
          color = styleTheme.palette.primary.main;

        var properties: GeoJsonProperties = {
          ['iconName']: getIconName(isOnGround, verticalRate, altitude, trueTrack),
          ['rotation']: getRotation(trueTrack, verticalRate, altitude),
          ['color']: color,
          ['isSelected']: isSelected,
          ['icao24']: stateVector.icao24,
          ['callsign']: callsign,
          ['altitude']: getFormattedValue(altitude, 1) + " m",
          ['velocity']: getFormattedValue(velocity, 1) + " km/h"
        }

        // Setup WGS84 coordinates
        var position: Position = [stateVector.longitude, stateVector.latitude];

        if (pathPredictions.length > 0) {

          const feature = pathPredictions.find(feature => feature.properties !== null && feature.properties['icao24']! === stateVector.icao24);
          if (feature)
            position = feature.geometry.coordinates;
        }

        var point: Point = {
          type: 'Point',
          coordinates: position
        };

        var feature: Feature<Point, GeoJsonProperties> = {
          type: 'Feature',
          id: `${index}.${stateVector.icao24}`,
          geometry: point,
          properties: properties
        }

        featureCollection.features.push(feature);
      }

      res(featureCollection);
    });

  };

  const getText = (): string | Expression | StyleFunction => {

    var text: string | Expression | StyleFunction = '';
    const simpleText = ["get", "callsign"] as Expression
    const detailedText = ['format',
      ["get", "callsign"], { "font-scale": 1.0 },
      "\n", {},
      ["get", "altitude"], { "font-scale": 0.75, "text-color": styleTheme.palette.text.primary },
      "\n", {},
      ["get", "velocity"], { "font-scale": 0.75, "text-color": styleTheme.palette.text.primary }
    ] as StyleFunction;

    if (props.zoom && props.zoom > 7)
      text = simpleText;
    if (props.zoom && props.zoom > 9)
      text = detailedText;

    return text;
  };

  const getSymbolLayout = () => {

    var showText = false;
    if (props.zoom && props.zoom > 7)
      showText = true;

    var isconSize = 1.0;
    if (props.zoom && props.zoom > 7)
      isconSize = 1.3;
    if (props.zoom && props.zoom > 9)
      isconSize = 1.6;

    const symbolLayout: SymbolLayout = {
      "icon-image": ["get", "iconName"],
      "icon-allow-overlap": true,
      "icon-rotate": ["get", "rotation"],
      "icon-size": isconSize,
      "text-field": showText ? getText() : '',
      "text-optional": true,
      "text-allow-overlap": true,
      "text-anchor": showText ? 'top' : 'center',
      "text-offset": showText ? [0, 1] : [0, 0]
    };

    return symbolLayout;
  };

  const getSymbolPaint = () => {

    var symbolPaint: SymbolPaint = {
      "icon-color": ["get", "color"],
      "text-color": ["get", "color"],
      "text-halo-width": 2,
      "text-halo-color": styleTheme.palette.background.default,
      "text-halo-blur": 2
    };

    return symbolPaint;
  };

  if (!props.stateVectors.states)
    return null;

  return (

    <Source
      type="geojson"
      data={featureCollection}>

      <Layer
        id={aircraftLayerId}
        type='symbol'
        source='geojson'
        layout={getSymbolLayout()}
        paint={getSymbolPaint()} />
    </Source>
  )
}