Skip to content

omnisense/omnilab API reference

Floor
Family
Version
1.x
Verified for real
Its README does not say
Licence
MIT
composer require omnisense/omnilab:1.x-dev

Read from the package's own PHP files at the commit cd8f6e2398d0: its namespaces, classes, interfaces and enums, their public constants, properties and methods with their PHPDoc, and the options its factories take. Nothing of it is run.

Omnilab

  • Collector class

    One thing from every source that knows it: the entity one source gives, then its cross-references followed into the other sources configured - PubChem's ChEBI and Wikidata ids offered to a ChEBI and a Wikidata source - and all of it merged, each property with the source it came from.

    4 methods · 1 property

  • Config class

    A source's configuration while its factory builds it: the options given, the factory's defaults under them, and the keys that must be filled.

    3 methods

  • Merger class

    The entities several sources describe, made one: identifiers, synonyms and sources united; each field (label, description, image...) from the source preferred for it; every property kept with the source that gave it, the preferred source's first when two give the same key.

    4 methods

  • Registry class

    The sources by name, each built once from its factory and options:

    7 methods

Omnilab\Bridge\Symfony

  • OmnilabBundle class

    Omnilab in a Symfony application: the source packages installed (omnilab/pubchem, omnilab/open-meteo...) registered, the sources built from configuration and injectable by their name, the Merger with the site's preferences and the Collector:

    2 methods

Omnilab\Exception

  • InvalidConfigException class

    A source misconfigured: a key missing, an unknown factory.

  • NotSupportedException class

    The source does not read that kind of identifier, or does not do that at all.

    2 methods

  • OmnilabException interface

    Every exception Omnilab throws.

  • ProviderException class

    The source's service answered with an error.

    1 method · 2 properties

  • RateLimitedException class

    The service asks to slow down (429), or the day's budget is spent (PubChem's per-minute budget, Open-Meteo's daily one).

    1 method · 1 property

  • UnavailableException class

    The service could not be reached, or is down: never "not found". A caller that caches an answer must not cache this one.

Omnilab\Http

  • Redaction class

    What a failed call may carry of its credentials, taken out before the failure leaves the package: an HTTP client's exception quotes the URL called (a key in its query, a password in its user part), and every exception's trace keeps the arguments of the calls under it (the request options, headers and query with the key) unless PHP runs with zend.exception_ignore_args.

    5 methods · 1 constant

Omnilab\Model

  • Asset class

    One file of a product, as a STAC asset: its address, its media type, its roles ("data", "thumbnail", "overview", "metadata", "visual"...). $href is kept as the catalogue gives it - an https:// or an s3:// address - and $alternates holds the other addresses of the same file by name ("https", "s3"). Whether reading it needs a token is the service's business: its README says.

    3 methods · 7 properties

  • BBox class

    An area: west, south, east and north bounds in WGS 84 degrees, in the order of GeoJSON and STAC. A box that crosses the antimeridian has its west bound greater than its east one.

    8 methods · 4 properties

  • Domain enum

    What an entity is a thing of. Coarse on purpose: a source says what kind of thing within its domain in Entity::$kind ("compound", "protein", "species", "galaxy", "earthquake"), in its own word.

    1 method · 6 cases

  • Entity class

    A thing science names and describes: a compound, a gene, a protein, a species, a star, an asteroid, an earthquake... as one source knows it, or as the Merger assembled it from several ($sources).

    9 methods · 15 properties

  • Identifier class

    One identifier, normalised so that two spellings of it compare equal: a ChEBI number without its "CHEBI:", a CAS number with its check digit checked, an Ensembl id without its version, a Wikidata item upper-cased.

    15 methods · 1 constant

  • Identifiers class

    An entity's identifiers, each once, in the order they were learnt.

    14 methods

  • Job class

    A request for a file of a dataset, as the service has it now: a Product in the making. The dataset is its collection; created, started and finished are its datetime, start and end; once successful, the file made is its "data" asset (file()). It adds the status (Job::ACCEPTED, RUNNING, SUCCESSFUL, FAILED, REJECTED, DISMISSED: the words of OGC API - Processes and of the data stores), the progress in percent when the service says, and why it failed when it says.

    4 methods · 3 properties · 7 constants

  • Point class

    A place on the Earth: WGS 84 latitude and longitude in degrees, the elevation in metres above sea level when known.

    5 methods · 3 properties

  • Product class

    A catalogued product: a satellite scene, a tile of a mosaic, a granule, a dataset's file - in the shape of a STAC item. Its footprint is a BBox and, when the catalogue gives it, the GeoJSON geometry; its properties are kept as the catalogue names them ("eo:cloud_cover", "platform", "sat:relative_orbit"...), its assets by name.

    5 methods · 12 properties

  • Property class

    One thing known of an entity: a key that does not change from one source to the next when they mean the same ("molecular_weight", "magnitude", "depth"), a label to show, a value - a Quantity when it has a unit, else a text, a number or a boolean - and the source that said it.

    5 methods · 4 properties

  • Quantity class

    A number with its unit, as the source gives it: "g/mol", "Ų", "°C", "km/h", "mm", "kg/m³". No conversion is made: a unit is a label here, in the spelling of the source that measured it (Open-Meteo's "°C", PubChem's "g/mol"); null for a pure number.

    3 methods · 3 properties

  • Scheme enum

    The kinds of identifiers the science bases give a thing, named as the bases themselves and identifiers.org name them. Each says the domain it belongs to (domain()), null for the general ones (Wikidata).

    2 methods · 67 cases

  • Series class

    One variable measured, modelled or forecast at a place over a time: the hourly temperature at 2 m in Strasbourg, the daily sum of precipitation, the monthly mean of NO2 over a box. Points are in time order; a value is null where the source has none.

    10 methods · 9 properties · 4 constants

Omnilab\Source

  • Capability enum

    What a source can be asked, and what its answers hold.

    15 cases

  • HttpSource class

    What the sources over HTTP share: the calls (GET, and POST for the services that take a query as a JSON body), their errors mapped the same way (404 and 204 are null; 429 a RateLimitedException; 5xx and transport errors an UnavailableException; any other 4xx a ProviderException), a minimum interval between two calls when the service asks for one (PubChem: five calls a second at most), and the reading of identifiers given as strings.

    8 methods

  • JobSourceInterface interface

    A source that makes files on request (Capability::JOBS): the Copernicus Climate and Atmosphere Data Stores cut a dataset to the variables, days and area asked, queue the request, then keep the file for a while. The source never waits: the application asks job() again until the job is done, then reads its file with open().

    3 methods

  • OccurrenceQuery class

    What to ask occurrences() for: the things found or happened at a place in a period - the observations of a species, the earthquakes of a region. The place is a BBox, or a Point and the radius around it in kilometres (null: the source's default, which its README states); no place is anywhere. The period has both ends included, a date without a time standing for its whole day; no end is open.

    3 methods · 8 properties · 8 constants

  • Page class

    One page of entities (search(), occurrences()) or of products (products()). $next is the cursor of the following page (pass it as the query's cursor), null on the last one.

    5 methods · 3 properties

  • ProductQuery class

    What to ask products() for. Every criterion is optional; a source applies those it understands and refuses $maxCloudCover without Capability::CLOUD_COVER, and a BBox without Capability::AREA, rather than answer unfiltered.

    2 methods · 8 properties · 2 constants

  • Query class

    What to ask search() for: a text, optionally the domain the caller means (a source that spans several - Wikidata - narrows to it; a source of one domain answers nothing for another), a page size and the cursor of the page before.

    2 methods · 4 properties

  • SeriesQuery class

    What to ask series() for: the variables, in the source's names ("temperature_2m", "precipitation"); the place - a Point, a BBox (Capability::AREA) or a station's Identifier (Capability::STATION: its own records, not the nearest one's); the period, both ends included (a date without a time stands for the whole day); the step (SeriesQuery::HOURLY, DAILY, or another ISO 8601 duration the source knows); the aggregation of a step longer than the measure (Series::MAX, MIN, MEAN, SUM); the time zone the times and the days are given in (null: the source's default, which its README states).

    2 methods · 7 properties · 3 constants

  • SourceFactory class

    The Omnibus way: a source's factory fills a Config - its name ("omnilab.factory_name"), the options it needs ("omnilab.required_options") and their defaults - then builds the source from it, on the application's HTTP client when one is given.

    3 options · 4 methods

  • SourceFactoryInterface interface

    Builds a source from its options (a contact address, a key, a base URI...).

    2 methods

  • SourceInterface interface

    A service that publishes what science knows or measures: PubChem, Open-Meteo, the Copernicus Data Space... whatever its domain. Its answers take one of four shapes:

    9 methods

  • Stac class

    The reading of a STAC API (stacspec.org, 1.0) that the catalogues share: a ProductQuery written as an item search's parameters, its answer read back into Products, a collection read as a dataset. A source makes the call itself (its getJson(), its throttle, its errors) and gives the answer here.

    4 methods · 2 constants

Omnilab

Collector class

final class Collector Collector.php:30

One thing from every source that knows it: the entity one source gives, then its cross-references followed into the other sources configured - PubChem's ChEBI and Wikidata ids offered to a ChEBI and a Wikidata source - and all of it merged, each property with the source it came from.

$caffeine = $collector->collect('pubchem', 'caffeine'); $caffeine->sources; // ['pubchem', 'chebi', 'wikidata']: those that answered $collector->incomplete; // ['chebi' => 'unreachable: ...'] the sources skipped: do not cache that answer

A source is offered each identifier in turn until one it reads names a thing it knows: a source that cannot read a scheme says so before any call (NotSupportedException), so only the schemes it reads cost a call. With $depth 2, what the second round found is followed in turn, into the sources not asked yet. A source that is down or rate limited is skipped and named in $incomplete; the others still answer.

public array $incomplete = []

Collector::__construct()

public function __construct(readonly Registry $registry, readonly Merger $merger = new Merger())

Collector::collect()

public function collect(string $source, Identifier|string $id, int $depth = 1, ?array $sources = null): ?Entity

The entity a source gives, completed by the others; null when that source does not know it.

$sources

the configured sources to follow into; null: all

Collector::follow()

public function follow(Entity $entity, int $depth = 1, ?array $sources = null): Entity

An entity completed by the sources that read its cross-references.

$sources

the configured sources to follow into; null: all

public function search(Query $query, ?array $sources = null): array

Every source that searches, asked the same query, their answers merged.

$sources

the configured sources to ask; null: all those that search

Config class

final class Config extends ArrayObject Config.php:13

A source's configuration while its factory builds it: the options given, the factory's defaults under them, and the keys that must be filled.

Config::defaults()

public function defaults(array $defaults): self

Sets each key not set yet (a null counts as not set).

Config::validateNotEmpty()

public function validateNotEmpty(array $keys): void

Config::get()

public function get(string $key, mixed $default = null): mixed

Merger class

final class Merger Merger.php:29

The entities several sources describe, made one: identifiers, synonyms and sources united; each field (label, description, image...) from the source preferred for it; every property kept with the source that gave it, the preferred source's first when two give the same key.

Two entities are the same thing when they share an identifier, unless one carries, in the scheme of the other's own id, only values the other does not know itself by: two PubChem compounds with two CIDs stay two, whatever CAS number they share (a salt and its base often do). What they are related to ($related: an organism, a ligand) does not join them.

Preferences name, for a field or a property key, the sources in the order they are trusted; "default" orders the others. A source not listed comes after those listed, in the order the entities were given:

new Merger(['default' => ['pubchem', 'chebi'], 'description' => ['chebi'], 'molecular_weight' => ['pubchem']]);

Merger::__construct()

public function __construct(array $preferences = [])
$preferences

field or property key => sources, "default" for the others

Merger::merge()

public function merge(iterable ...$lists): array

Each source's entities, grouped by the thing they describe.

Returns

in the order each thing was first seen

Merger::same()

public function same(Entity $a, Entity $b): bool

Whether the Merger takes two entities for the same thing.

Merger::combine()

public function combine(Entity ...$entities): Entity

One entity from several that describe the same thing (the Collector found them through each other's identifiers): no check is made.

Registry class

final class Registry Registry.php:18

The sources by name, each built once from its factory and options:

new Registry([new PubChemSourceFactory($http), new OpenMeteoSourceFactory($http)], [ 'pubchem' => ['factory' => 'pubchem', 'options' => ['mailto' => 'support@glitchr.io']], 'meteo' => ['factory' => 'open-meteo'], 'meteo-pro' => ['factory' => 'open-meteo', 'options' => ['api_key' => '...']], ]);

Registry::__construct()

public function __construct(iterable $factories, readonly array $config)

Registry::get()

public function get(string $name): SourceInterface

Registry::has()

public function has(string $name): bool

Registry::names()

public function names(): array
Returns

the configured sources, in the configured order

Registry::all()

public function all(): array
Returns

in the configured order

Registry::factories()

public function factories(): array
Returns

the factories installed: what factory: may name

Registry::options()

public function options(string $name): array

The options a source is configured with (its factory's defaults not included).

Omnilab\Bridge\Symfony

OmnilabBundle class

final class OmnilabBundle extends AbstractBundle OmnilabBundle.php:95

Omnilab in a Symfony application: the source packages installed (omnilab/pubchem, omnilab/open-meteo...) registered, the sources built from configuration and injectable by their name, the Merger with the site's preferences and the Collector:

omnilab: sources: pubchem: { factory: pubchem, options: { mailto: '%env(OMNILAB_MAILTO)%' } } meteo: { factory: open-meteo } copernicus: { factory: copernicus } merger: preferences: default: [pubchem] description: [chebi, pubchem]

public function __construct(SourceInterface $pubchem, SourceInterface $meteo, Collector $collector) }

An application's own factories (a SourceFactoryInterface) are registered too, autoconfigured.

OmnilabBundle::configure()

public function configure(DefinitionConfigurator $definition): void

OmnilabBundle::loadExtension()

public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void

Omnilab\Exception

InvalidConfigException class

final class InvalidConfigException extends LogicException implements OmnilabException InvalidConfigException.php:6

A source misconfigured: a key missing, an unknown factory.

NotSupportedException class

final class NotSupportedException extends LogicException implements OmnilabException NotSupportedException.php:6

The source does not read that kind of identifier, or does not do that at all.

NotSupportedException::identifier()

public static function identifier(string $source, string $identifier, string $what = 'look up'): self

NotSupportedException::operation()

public static function operation(string $source, string $operation): self

OmnilabException interface

interface OmnilabException extends Throwable OmnilabException.php:6

Every exception Omnilab throws.

ProviderException class

class ProviderException extends RuntimeException implements OmnilabException ProviderException.php:6

The source's service answered with an error.

public readonly string $source
public readonly ?int $status

ProviderException::__construct()

public function __construct(public readonly string $source, string $message, public readonly ?int $status = null, ?\Throwable $previous = null)

RateLimitedException class

final class RateLimitedException extends UnavailableException RateLimitedException.php:6

The service asks to slow down (429), or the day's budget is spent (PubChem's per-minute budget, Open-Meteo's daily one).

public readonly ?int $retryAfter

RateLimitedException::__construct()

public function __construct(string $source, public readonly ?int $retryAfter = null, ?\Throwable $previous = null)

UnavailableException class

class UnavailableException extends ProviderException UnavailableException.php:9

The service could not be reached, or is down: never "not found". A caller that caches an answer must not cache this one.

Omnilab\Http

Redaction class

final class Redaction Redaction.php:22

What a failed call may carry of its credentials, taken out before the failure leaves the package: an HTTP client's exception quotes the URL called (a key in its query, a password in its user part), and every exception's trace keeps the arguments of the calls under it (the request options, headers and query with the key) unless PHP runs with zend.exception_ignore_args.

copy() gives the previous exception to wrap instead of the client's own: the same class and code, its message with the key's parameters, the user part and every secret value replaced by ***, its trace without arguments, its own previous copied the same way. Properties of the client's class beyond those of \Exception are not copied (a Symfony HttpException's response, a PSR-18 exception's request: their URL holds the key). withoutArguments() takes the arguments out of the trace of the exception thrown in its place.

const MASK = '***'

Redaction::secrets()

public static function secrets(string $url, array $options = []): array

The secrets a request carries: the values of its query parameters, headers and body fields named as credentials (key, token, secret, password, signature, auth...), a bearer token, a basic password, the URL's user part.

$options

the HTTP client's options

Redaction::sensitive()

public static function sensitive(string $name): bool

Whether a parameter, header or field of this name holds a credential.

Redaction::text()

public static function text(string $text, array $secrets = []): string

A text without credentials: in every address, the user part and the parameters named as credentials masked (api_key=***); then every secret given, raw or URL-encoded, masked wherever it stands.

Redaction::copy()

public static function copy(\Throwable $failure, array $secrets = []): \Throwable

A copy of a failure to wrap in its place: its class, code, file and line, its message through text(), its trace without arguments, its previous copied the same way.

Redaction::withoutArguments()

public static function withoutArguments(\Throwable $failure): \Throwable

The failure given, the arguments out of its trace (and out of its previous ones'), as zend.exception_ignore_args would have left it.

Omnilab\Model

Asset class

final readonly class Asset Asset.php:13

One file of a product, as a STAC asset: its address, its media type, its roles ("data", "thumbnail", "overview", "metadata", "visual"...). $href is kept as the catalogue gives it - an https:// or an s3:// address - and $alternates holds the other addresses of the same file by name ("https", "s3"). Whether reading it needs a token is the service's business: its README says.

public string $name
public string $href
public ?string $type
public array $roles
public ?string $title
public ?int $size
public array $alternates

Asset::__construct()

public function __construct(public string $name, public string $href, public ?string $type = null, public array $roles = [], public ?string $title = null, public ?int $size = null, public array $alternates = [])

Asset::hasRole()

public function hasRole(string $role): bool

Asset::httpsHref()

public function httpsHref(): ?string

The first https:// address: $href when it is one, else an alternate.

BBox class

final readonly class BBox implements Stringable BBox.php:12

An area: west, south, east and north bounds in WGS 84 degrees, in the order of GeoJSON and STAC. A box that crosses the antimeridian has its west bound greater than its east one.

new BBox(7.68, 48.53, 7.83, 48.64); // Strasbourg

public float $west
public float $south
public float $east
public float $north

BBox::__construct()

public function __construct(public float $west, public float $south, public float $east, public float $north)

BBox::parse()

public static function parse(string $text): self

"7.68,48.53,7.83,48.64": west, south, east, north.

BBox::fromArray()

public static function fromArray(array $bbox): self
$bbox

a GeoJSON bbox (a 3D one's elevations dropped)

BBox::contains()

public function contains(Point $point): bool

BBox::center()

public function center(): Point

BBox::toArray()

public function toArray(): array
Returns

west, south, east, north

BBox::toGeoJson()

public function toGeoJson(): array

As a GeoJSON polygon.

BBox::__toString()

public function __toString(): string

Domain enum

enum Domain Domain.php:10

What an entity is a thing of. Coarse on purpose: a source says what kind of thing within its domain in Entity::$kind ("compound", "protein", "species", "galaxy", "earthquake"), in its own word.

case CHEMISTRY = 'chemistry'

Compounds, substances, elements, reactions.

case BIOLOGY = 'biology'

Genes, proteins, structures, pathways.

case TAXONOMY = 'taxonomy'

Species and the other ranks of the tree of life, and where they were found (occurrences).

case ASTRONOMY = 'astronomy'

Stars, galaxies, planets, small bodies.

case EARTH = 'earth'

The solid earth, its oceans and its atmosphere: earthquakes, volcanoes, stations, places.

case OTHER = 'other'

What none of the above holds.

Domain::label()

public function label(): string

Entity class

final readonly class Entity Entity.php:25

A thing science names and describes: a compound, a gene, a protein, a species, a star, an asteroid, an earthquake... as one source knows it, or as the Merger assembled it from several ($sources).

$id is the identifier the source keeps it under; $identifiers holds every identifier known for it, its own first, then the cross-references to the other bases (ChEBI, CAS, Wikidata...) that the Collector follows. A property says which source it came from: after a merge, two sources' values for one key are both kept, the preferred first.

$related names other things it is tied to, by relation: a protein's organism (ncbi.taxon:9606), its structures (pdb:1A3N), the ligands it binds (chebi:60344). They are not its identifiers: the Collector does not follow them, and the Merger does not take two entities for one thing because they share them.

$time and $place are set for what happens somewhere at a moment (an earthquake, an eruption, an observation) or sits somewhere (a station).

public Identifier $id
public string $label
public Domain $domain
public ?string $kind

the source's word for what it is: "compound", "protein", "species", "galaxy", "earthquake"

public array $synonyms

the other names it goes by

public ?string $description
public array $properties
public Identifiers $identifiers
public ?string $url
public ?string $image
public ?\DateTimeImmutable $time
public Point|BBox|null $place
public ?string $source
public array $sources

the sources merged into this record

other things it is tied to, by relation ("organism", "structure", "ligand"...)

Entity::__construct()

public function __construct(public Identifier $id, public string $label, public Domain $domain = Domain::OTHER, public ?string $kind = null, public array $synonyms = [], public ?string $description = null, public array $properties = [], public Identifiers $identifiers = new Identifiers(), public ?string $url = null, public ?string $image = null, public ?\DateTimeImmutable $time = null, public Point|BBox|null $place = null, public ?string $source = null, public array $sources = [], public array $related = [])
$kind

the source's word for what it is: "compound", "protein", "species", "galaxy", "earthquake"

$synonyms

the other names it goes by

$sources

the sources merged into this record

$related

other things it is tied to, by relation ("organism", "structure", "ligand"...)

Entity::with()

public function with(array $changes): self

A copy with some fields changed: $entity->with(['description' => $text]).

Entity::identifiers()

public function identifiers(): Identifiers

Every identifier, its own included, each once.

Entity::identifier()

public function identifier(Scheme $scheme): ?string

The first value of a scheme: $compound->identifier(Scheme::CAS) is "58-08-2".

public function related(string $relation): Identifiers

The identifiers of the things tied to it by a relation: $protein->related('organism')->value(Scheme::NCBI_TAXON) is "9606".

Entity::property()

public function property(string $key): ?Property

The property of that key from the preferred source; null when none is known.

Entity::properties()

public function properties(string $key): array
Returns

every source's property of that key, the preferred first

Entity::value()

public function value(string $key): Quantity|string|int|float|bool|null

A property's value: $compound->value('molecular_formula') is "C8H10N4O2", ->value('molecular_weight') a Quantity.

Entity::keys()

public function keys(): array
Returns

the keys known, each once, in the order first given

Identifier class

final readonly class Identifier implements Stringable Identifier.php:19

One identifier, normalised so that two spellings of it compare equal: a ChEBI number without its "CHEBI:", a CAS number with its check digit checked, an Ensembl id without its version, a Wikidata item upper-cased.

Identifier::parse('CHEBI:27732'); // chebi:27732 Identifier::parse('58-08-2'); // cas:58-08-2 Identifier::parse('RYYVLZVUVIJVGH-UHFFFAOYSA-N'); // inchikey:RYYVLZVUVIJVGH-UHFFFAOYSA-N Identifier::parse('https://pubchem.ncbi.nlm.nih.gov/compound/2519'); // pubchem.cid:2519 Identifier::parse('https://identifiers.org/uniprot:P69905'); // uniprot:P69905 Identifier::of(Scheme::PUBCHEM_CID, '2519')->url(); // https://pubchem.ncbi.nlm.nih.gov/compound/2519 Identifier::tryOf('cas', '58-08-3'); // null: wrong check digit Identifier::of('wmo', '07190'); // wigos:0-20000-0-07190

const ENSEMBL_GENOMES = [['~^FB(?:gn|tr|pp)\d{7}$~i', 'FB', 'lower', 'drosophila_melanogaster'], ['~^WBGene\d{8}$~i', 'WBGene', 'keep', 'caenorhabditis_elegans'], ['~^Y[A-P][LR]\d{3…

The genomes Ensembl serves under their community's ids, told by the shape of their ids: the pattern (case aside), the prefix as written, the case of the rest, the genome (Ensembl's production name, which a lookup needs). Each checked on 2026-10-15 against rest.ensembl.org.

Identifier::of()

public static function of(Scheme|string $scheme, string $value): self
Throws

InvalidArgumentException when the value is not a valid identifier of that scheme

Identifier::tryOf()

public static function tryOf(Scheme|string|null $scheme, int|string|null $value): ?self

The identifier, or null when the value is not one of that scheme.

Identifier::parse()

public static function parse(string $input): ?self

Reads an identifier from a string: "scheme:value" (the schemes' own names, identifiers.org's prefixes, CHEBI:27732), a page of the base that holds it or an identifiers.org link, or a value whose shape tells its scheme (an InChIKey, a CAS number, CHEMBL113, DTXSID..., an Ensembl id, a Wikidata Q-number). Null when nothing is recognised: a bare number (a CID? a GBIF key? a gene?) needs its scheme, and so does a UniProt accession or a PDB code, whose shapes other values share.

Identifier::scheme()

public static function scheme(string $name): ?Scheme

The scheme a name or a prefix stands for: "chebi", "pubchem.cid", "ncbigene", "CHEBI".

..

Identifier::cid()

public static function cid(int|string $value): self

Identifier::chebi()

public static function chebi(int|string $value): self

Identifier::inchikey()

public static function inchikey(string $value): self

Identifier::cas()

public static function cas(string $value): self

Identifier::wikidata()

public static function wikidata(string $value): self

Identifier::key()

public function key(): string

"chebi:27732": what the Merger and the collections key on.

Identifier::display()

public function display(): string

The identifier as its base writes it: CHEBI:27732, CID 2519, 58-08-2.

Identifier::equals()

public function equals(self $other): bool

Identifier::url()

public function url(): ?string

The page of the base that holds it, when there is one.

Identifier::__toString()

public function __toString(): string

Identifier::ensemblSpecies()

public static function ensemblSpecies(self|string $id): ?string

The genome (Ensembl's production name) of an id of the ensembl.genomes scheme, when its shape tells it: "drosophila_melanogaster" for FBgn0000008; null otherwise.

Identifiers class

final readonly class Identifiers implements IteratorAggregate, Countable Identifiers.php:10

An entity's identifiers, each once, in the order they were learnt.

Identifiers::__construct()

public function __construct(iterable $identifiers = [])
$identifiers

nulls (an identifier that did not parse) are skipped

Identifiers::of()

public static function of(?Identifier ...$identifiers): self

Identifiers::get()

public function get(Scheme $scheme): ?Identifier

The first identifier of that scheme.

Identifiers::value()

public function value(Scheme $scheme): ?string

The first value of that scheme: $entity->identifiers->value(Scheme::CHEBI).

Identifiers::all()

public function all(?Scheme $scheme = null): array
Returns

every identifier, or those of one scheme

Identifiers::values()

public function values(Scheme $scheme): array
Returns

the values of one scheme

Identifiers::has()

public function has(Scheme|Identifier $what): bool

Identifiers::with()

public function with(?Identifier ...$identifiers): self

Identifiers::merge()

public function merge(self $other): self

Identifiers::keys()

public function keys(): array
Returns

"chebi:27732"...

Identifiers::toArray()

public function toArray(): array
Returns

values by scheme, for storage

Identifiers::fromArray()

public static function fromArray(array $array): self
$array

what toArray() gave

Identifiers::getIterator()

public function getIterator(): \ArrayIterator

Identifiers::count()

public function count(): int

Job class

final readonly class Job extends Product Job.php:17

A request for a file of a dataset, as the service has it now: a Product in the making. The dataset is its collection; created, started and finished are its datetime, start and end; once successful, the file made is its "data" asset (file()). It adds the status (Job::ACCEPTED, RUNNING, SUCCESSFUL, FAILED, REJECTED, DISMISSED: the words of OGC API - Processes and of the data stores), the progress in percent when the service says, and why it failed when it says.

while (!$job->isDone()) { sleep(10); $job = $source->job($job->id); } $job->isSuccessful() && $source->open($job->file());

const ACCEPTED = 'accepted'
const RUNNING = 'running'
const SUCCESSFUL = 'successful'
const FAILED = 'failed'
const REJECTED = 'rejected'
const DISMISSED = 'dismissed'
const ENDS = [self::SUCCESSFUL, self::FAILED, self::REJECTED, self::DISMISSED]

The states a request does not leave.

public string $status
public ?float $progress

percent, when the service says

public ?string $message

Job::__construct()

public function __construct(string $id, string $dataset, public string $status, public ?float $progress = null, public ?string $message = null, ?\DateTimeImmutable $created = null, ?\DateTimeImmutable $started = null, ?\DateTimeImmutable $finished = null, array $properties = [], ?Asset $file = null, ?string $url = null, ?string $source = null)
$dataset

the dataset the file is made of (the Product's collection)

$progress

percent, when the service says

$properties

the service's own (the request, a checksum...)

Job::isDone()

public function isDone(): bool

Ended, well or not: successful, failed, rejected or dismissed.

Job::isSuccessful()

public function isSuccessful(): bool

Job::file()

public function file(): ?Asset

The file made, once successful: open() reads it.

Point class

final readonly class Point implements Stringable Point.php:11

A place on the Earth: WGS 84 latitude and longitude in degrees, the elevation in metres above sea level when known.

new Point(48.5734, 7.7521); // Strasbourg

public float $latitude
public float $longitude
public ?float $elevation

Point::__construct()

public function __construct(public float $latitude, public float $longitude, public ?float $elevation = null)

Point::parse()

public static function parse(string $text): self

"48.5734,7.7521": latitude first, as people write it.

Point::around()

public function around(float $kilometres): BBox

The box of the given half-size around it, in kilometres (approximate away from the poles).

Point::toGeoJson()

public function toGeoJson(): array

As GeoJSON: longitude first.

Point::__toString()

public function __toString(): string

Product class

readonly class Product Product.php:17

A catalogued product: a satellite scene, a tile of a mosaic, a granule, a dataset's file - in the shape of a STAC item. Its footprint is a BBox and, when the catalogue gives it, the GeoJSON geometry; its properties are kept as the catalogue names them ("eo:cloud_cover", "platform", "sat:relative_orbit"...), its assets by name.

$datetime is the nominal time; $start and $end the interval when the product covers one (a mosaic, an average).

Not final: a Job (a product being made on request) extends it.

public string $id
public string $collection
public ?\DateTimeImmutable $datetime
public ?\DateTimeImmutable $start
public ?\DateTimeImmutable $end
public ?BBox $bbox
public ?array $geometry

GeoJSON

public array $properties
public array $assets
public ?string $preview
public ?string $url
public ?string $source

Product::__construct()

public function __construct(public string $id, public string $collection, public ?\DateTimeImmutable $datetime = null, public ?\DateTimeImmutable $start = null, public ?\DateTimeImmutable $end = null, public ?BBox $bbox = null, public ?array $geometry = null, public array $properties = [], public array $assets = [], public ?string $preview = null, public ?string $url = null, public ?string $source = null)
$geometry

GeoJSON

Product::cloudCover()

public function cloudCover(): ?float

The cloud cover in percent, for an optical scene that says ("eo:cloud_cover").

Product::platform()

public function platform(): ?string

"sentinel-2a", "landsat-9"... ("platform").

Product::asset()

public function asset(string $name): ?Asset

Product::assets()

public function assets(string $role): array
Returns

the assets that have that role

Property class

final readonly class Property implements Stringable Property.php:14

One thing known of an entity: a key that does not change from one source to the next when they mean the same ("molecular_weight", "magnitude", "depth"), a label to show, a value - a Quantity when it has a unit, else a text, a number or a boolean - and the source that said it.

new Property('molecular_weight', 'Molecular weight', new Quantity(194.19, 'g/mol'), 'pubchem'); new Property('molecular_formula', 'Molecular formula', 'C8H10N4O2', 'pubchem');

public string $key
public string $label
public Quantity|string|int|float|bool $value
public ?string $source

Property::__construct()

public function __construct(public string $key, public string $label, public Quantity|string|int|float|bool $value, public ?string $source = null)

Property::number()

public function number(): int|float|null

The number, a Quantity's included; null for a text or a boolean.

Property::unit()

public function unit(): ?string

A Quantity's unit; null otherwise.

Property::with()

public function with(array $changes): self

Property::__toString()

public function __toString(): string

The value as text: "194.19 g/mol", "C8H10N4O2", "yes".

Quantity class

final readonly class Quantity implements Stringable Quantity.php:15

A number with its unit, as the source gives it: "g/mol", "Ų", "°C", "km/h", "mm", "kg/m³". No conversion is made: a unit is a label here, in the spelling of the source that measured it (Open-Meteo's "°C", PubChem's "g/mol"); null for a pure number.

new Quantity(194.19, 'g/mol'); // "194.19 g/mol" new Quantity(4.6, 'km', uncertainty: 0.2); // "4.6 ± 0.2 km" new Quantity(1.67262192595e-27, 'kg', 5.2e-37); // "1.67262192595e-27 ± 0.00000000052e-27 kg"

public int|float $value
public ?string $unit
public ?float $uncertainty

Quantity::__construct()

public function __construct(public int|float $value, public ?string $unit = null, public ?float $uncertainty = null)

Quantity::scaled()

public function scaled(float $factor, ?string $unit): self

The same number in another unit, when the caller knows the factor: $metres->scaled(0.001, 'km').

Quantity::__toString()

public function __toString(): string

The number as written for a reader: "194.19 g/mol", "4.6 ± 0.2 km"; a float below 1e-4 or from 1e9 up in scientific notation with every significant digit it holds, its uncertainty in the same exponent, as CODATA writes them: "1.67262192595e-27 ± 0.00000000052e-27 kg". An integer as it is; an uncertainty a fixed notation would round to 0 in scientific notation of its own.

Scheme enum

enum Scheme Scheme.php:10

The kinds of identifiers the science bases give a thing, named as the bases themselves and identifiers.org name them. Each says the domain it belongs to (domain()), null for the general ones (Wikidata).

case PUBCHEM_CID = 'pubchem.cid'

A PubChem compound: 2519 (caffeine).

case CHEBI = 'chebi'

ChEBI, the EBI's chemical entities of biological interest: 27732, written CHEBI:27732.

case CHEMBL = 'chembl'

ChEMBL, bioactive molecules: CHEMBL113.

case INCHIKEY = 'inchikey'

The hashed InChI: RYYVLZVUVIJVGH-UHFFFAOYSA-N.

case INCHI = 'inchi'

The IUPAC International Chemical Identifier itself: InChI=1S/C8H10N4O2/.

..

case CAS = 'cas'

A CAS Registry Number: 58-08-2, its check digit checked.

case UNII = 'unii'

The FDA's Unique Ingredient Identifier: 3G6A5W338E.

case DSSTOX = 'dsstox'

The EPA's DSSTox substance (CompTox dashboard): DTXSID0020232.

case KEGG = 'kegg'

A KEGG compound (C07481) or drug (D00528).

case KEGG_REACTION = 'kegg.reaction'

A KEGG reaction: R00200 (pyruvate kinase's).

case KEGG_PATHWAY = 'kegg.pathway'

A KEGG pathway map, reference (map00010) or of an organism (hsa00010), or its KO, enzyme or reaction view (ko00010, ec00010, rn00010).

case RHEA = 'rhea'

A Rhea biochemical reaction, its number: 18157, written RHEA:18157 (any of a reaction's four ids: the master, its two directions, both).

case EC = 'ec'

An enzyme's EC number of the IUBMB's Enzyme Nomenclature, complete or not: 2.7.1.40 (pyruvate kinase), 2.7.1.-, written EC 2.7.1.40.

case COD = 'cod'

A crystal structure of the Crystallography Open Database, its seven digits: 1000000 (Wikidata's P9824).

case MATERIALS_PROJECT = 'materialsproject'

A material of the Materials Project, its computed structure: mp-149 (silicon; Wikidata's P9902).

case OQMD = 'oqmd'

An entry of the Open Quantum Materials Database, its number: 6 (OQMD's own id of a computed structure).

case CODATA = 'codata'

A fundamental physical constant of CODATA, by NIST's short name: mp (the proton mass), c, h (Wikidata's P1645).

case UNIPROT = 'uniprot'

A UniProt protein: P69905.

case NCBI_GENE = 'ncbi.gene'

An NCBI Gene record: 7157 (TP53, human).

case NCBI_TAXON = 'ncbi.taxon'

An NCBI Taxonomy node: 9606 (Homo sapiens).

case ENSEMBL = 'ensembl'

An Ensembl gene, transcript or protein, without its version: ENSG00000141510.

case ENSEMBL_GENOMES = 'ensembl.genomes'

An Ensembl gene, transcript or protein of a genome Ensembl annotates under its community's ids (FlyBase, WormBase, SGD, Araport, PomBase...): FBgn0000008, AT5G10140, YAL001C.

case PDB = 'pdb'

A Protein Data Bank structure: 1A3N.

case GBIF = 'gbif'

A GBIF taxon: the key of the Catalogue of Life eXtended Release, GBIF's taxonomy since June 2026 (4CGXP, Panthera leo), or the number of the legacy GBIF Backbone (5219404, the same lion).

case GBIF_OCCURRENCE = 'gbif.occurrence'

A GBIF occurrence: one record of an organism at a place and a time, 5938104699.

case INATURALIST = 'inaturalist'

An iNaturalist taxon: 42007 (Puma concolor).

case INATURALIST_OBSERVATION = 'inaturalist.observation'

An iNaturalist observation: one record of an organism by a person at a place and a time, 332912788.

case WORMS = 'worms'

A WoRMS taxon (World Register of Marine Species), by its AphiaID, which OBIS uses too: 137094 (Delphinus delphis).

case OBIS_OCCURRENCE = 'obis.occurrence'

An OBIS occurrence: one record of a marine organism at a place and a time, by its UUID, 00007235-5211-4633-98bb-0f8f9ad53e0f.

case ITIS = 'itis'

An ITIS taxon (Integrated Taxonomic Information System), by its TSN: 180438 (Delphinus delphis).

case COL = 'col'

A Catalogue of Life taxon or name usage, by its stable id (ChecklistBank): 4CGXP (Panthera leo) - the key GBIF gives the same taxon since June 2026.

case PBDB = 'pbdb'

A taxon of the Paleobiology Database, by its number: 54833 (Tyrannosaurus rex), written txn:54833.

case PBDB_OCCURRENCE = 'pbdb.occurrence'

A fossil occurrence of the Paleobiology Database, by its number: 139292, written occ:139292.

case EBIRD = 'ebird'

An eBird taxon (the Clements/eBird taxonomy), by its species code: amerob (American Robin).

case EBIRD_CHECKLIST = 'ebird.checklist'

An eBird checklist, the record of one birding outing at a place and a time: S123456789.

case IUCN = 'iucn'

A taxon of the IUCN Red List, by its SIS id: 134817215 (Delphinus delphis).

case SIMBAD = 'simbad'

An object of SIMBAD, the CDS astronomical database, by its main identifier: M 31.

case JPL_SBDB = 'jpl.sbdb'

A small body of JPL's database (asteroid, comet): 433, 1P/Halley, 2004 MN4.

case MPC = 'mpc'

A minor planet, comet or natural satellite as the IAU's Minor Planet Center designates it: its number (433), else its designation (2024 YR4, 1P, C/2020 F3); Wikidata's P5736.

case NED = 'ned'

An object of NED, the NASA/IPAC Extragalactic Database, by its preferred name: Messier 031, 3C 273.

case DONKI = 'donki'

An event of NASA's DONKI (space weather: a solar flare, a coronal mass ejection, a geomagnetic storm...) by its activity id: 2026-10-04T06:13:00-FLR-001.

case LAUNCH_LIBRARY = 'launchlibrary'

A launch of The Space Devs' Launch Library 2 by its id, a UUID: df88cfbf-f504-4e11-8370-e33dd6280b5b.

case USGS_EVENT = 'usgs.event'

A USGS earthquake event (ComCat): us6000u24c, official20110311054624120_30.

case REACTOME = 'reactome'

A Reactome pathway, reaction or physical entity, by its stable identifier without its version: R-HSA-70171 (glycolysis, human).

case STRING = 'string'

A protein of STRING, the network of protein interactions, by its STRING id: the NCBI taxon, a dot, the protein's id in its genome (9606.ENSP00000269305, TP53).

case INTERPRO = 'interpro'

An InterPro entry (family, domain, site, repeat, homologous superfamily): IPR000971.

case PFAM = 'pfam'

A Pfam family or domain, one of InterPro's member databases: PF00042.

case GO = 'go'

A Gene Ontology term (function, process, component), its number: 0005829, written GO:0005829.

case HGNC = 'hgnc'

A human gene of the HUGO Gene Nomenclature Committee, its number: 4827, written HGNC:4827 (HBB).

case OMIM = 'omim'

An entry of OMIM, Online Mendelian Inheritance in Man, a gene or a phenotype, by its MIM number: 141900.

case EFO = 'efo'

A term of the Experimental Factor Ontology (diseases, traits, measurements), its number: 0000305, written EFO:0000305.

case MONDO = 'mondo'

A disease of the Mondo Disease Ontology, its number: 0011382, written MONDO:0011382 (sickle cell disease).

case ORPHANET = 'orphanet'

A rare disease of Orphanet, its ORPHAcode: 232, written ORPHA:232.

case HP = 'hp'

A phenotypic abnormality of the Human Phenotype Ontology, its number: 0001250, written HP:0001250.

case CLINVAR = 'clinvar'

A variant of ClinVar by its VariationID: 15333, written VCV000015333.

case DBSNP = 'dbsnp'

A short variant of dbSNP by its reference SNP id: rs334.

case GWAS = 'gwas'

A study of the NHGRI-EBI GWAS Catalog: GCST000854.

case GAIA = 'gaia'

A source of ESA's Gaia DR3 by its source_id: 4472832130942575872, written Gaia DR3 4472832130942575872 (Barnard's star).

case EXOPLANET = 'exoplanet'

A planet or a planetary system of the NASA Exoplanet Archive, by the archive's name: 51 Peg b, Proxima Cen (Wikidata's P5667).

case GHCN = 'ghcn'

A station of the Global Historical Climatology Network (NCEI): USW00094728, New York's Central Park.

case WIGOS = 'wigos'

A station of the WMO's register by its WIGOS id: 0-20000-0-07190, Strasbourg-Entzheim (a WMO index, 07190, read as such).

case NWS_STATION = 'nws.station'

An observation station of the U.S. National Weather Service: KNYC.

case EMSC_EVENT = 'emsc.event'

An event of the EMSC's catalogue by its SeismicPortal id: 20261004_0000156 (the day, then a number).

case USGS_SITE = 'usgs.site'

A water monitoring location of the USGS (streamgage, well, spring) by its id in the Water Data APIs: USGS-01646500, the Potomac at Little Falls.

case COOPS_STATION = 'coops.station'

A water level station of NOAA's CO-OPS (Tides and Currents) by its seven digits: 8518750, The Battery, New York.

case DWD_STATION = 'dwd.station'

A station of the Deutscher Wetterdienst by its DWD id, five characters: 01420, Frankfurt/Main.

case WIKIDATA = 'wikidata'

A Wikidata item, whatever its domain: Q60235.

Scheme::label()

public function label(): string

Scheme::domain()

public function domain(): ?Domain

The domain of the things this scheme names; null when it names anything (Wikidata).

Series class

final readonly class Series implements IteratorAggregate, Countable Series.php:22

One variable measured, modelled or forecast at a place over a time: the hourly temperature at 2 m in Strasbourg, the daily sum of precipitation, the monthly mean of NO2 over a box. Points are in time order; a value is null where the source has none.

$series->variable; // "temperature_2m" $series->unit; // "°C", as the source spells it foreach ($series as [$time, $value]) { ... } $series->max(); // 20.4

$step is an ISO 8601 duration ("PT1H", "PT15M", "P1D"), null when the points are irregular; $aggregation says what a point of a step stands for (Series::MEAN, MAX, MIN, SUM), null for an instant value.

const MEAN = 'mean'
const MAX = 'max'
const MIN = 'min'
const SUM = 'sum'
public string $variable
public ?string $unit
public Point|BBox $place
public array $points
public ?string $step
public ?string $aggregation
public ?string $label
public ?bool $forecast

true when the points are a forecast, false when observed or reanalysed, null when mixed or unknown

public ?string $source

Series::__construct()

public function __construct(public string $variable, public ?string $unit, public Point|BBox $place, public array $points = [], public ?string $step = null, public ?string $aggregation = null, public ?string $label = null, public ?bool $forecast = null, public ?string $source = null)
$forecast

true when the points are a forecast, false when observed or reanalysed, null when mixed or unknown

Series::values()

public function values(): array

Series::times()

public function times(): array

Series::at()

public function at(\DateTimeInterface $time): int|float|null

The value at that exact time; null when there is no point then, or no value.

Series::min()

public function min(): int|float|null

Series::max()

public function max(): int|float|null

Series::mean()

public function mean(): ?float

Series::between()

public function between(\DateTimeInterface $from, \DateTimeInterface $to): self

The points from $from (included) to $to (excluded).

Series::getIterator()

public function getIterator(): \ArrayIterator

Series::count()

public function count(): int

Omnilab\Source

Capability enum

enum Capability Capability.php:6

What a source can be asked, and what its answers hold.

case ENTITY = 'entity'

entity(): one thing by an identifier (or a name, where the source reads names).

search(): things by text, one page at a time.

case SERIES = 'series'

series(): a variable at a place over a time.

case PRODUCTS = 'products'

products(): the catalogued products over a place and a time, one page at a time.

case OCCURRENCES = 'occurrences'

occurrences(): the things found or happened at a place in a period - each an entity with its time and its place -, one page at a time.

case DATASETS = 'datasets'

datasets(): the catalogue's datasets by text, one page at a time.

case JOBS = 'jobs'

The source implements JobSourceInterface: submit() a request for a file, job() and jobs() follow it.

case OPEN = 'open'

open(): a file read with the source's credentials (a token, a key).

case STRUCTURE = 'structure'

An entity's image is its structure: a 2D depiction of a molecule, a 3D model of a protein.

case CROSS_REFERENCES = 'cross_references'

An entity carries identifiers of other bases, which the Collector can follow.

case FORECAST = 'forecast'

series() answers ahead of now: a forecast.

case HISTORICAL = 'historical'

series() answers for the past: observations, an archive, a reanalysis.

case AREA = 'area'

series(), products() or occurrences() take a BBox, not only a Point; a source without it refuses a box rather than answer for its centre.

case STATION = 'station'

series() takes a station's Identifier (ghcn, wigos, nws.station) in place of a Point, and answers that station's records; a source without it refuses a station.

case CLOUD_COVER = 'cloud_cover'

products() applies ProductQuery::$maxCloudCover; a source without it refuses the query rather than answer unfiltered.

HttpSource class

abstract class HttpSource implements SourceInterface HttpSource.php:28

What the sources over HTTP share: the calls (GET, and POST for the services that take a query as a JSON body), their errors mapped the same way (404 and 204 are null; 429 a RateLimitedException; 5xx and transport errors an UnavailableException; any other 4xx a ProviderException), a minimum interval between two calls when the service asks for one (PubChem: five calls a second at most), and the reading of identifiers given as strings.

What a source does not override it does not do: NotSupportedException.

HttpSource::__construct()

public function __construct(protected readonly HttpClientInterface $http, protected readonly string $baseUri, protected readonly array $headers = [], protected readonly float $throttle = 0.0)
$headers

sent with every call (User-Agent...)

$throttle

seconds to leave between two calls

HttpSource::entity()

public function entity(Identifier|string $id): ?Entity
public function search(Query $query): Page

HttpSource::series()

public function series(SeriesQuery $query): array

HttpSource::products()

public function products(ProductQuery $query): Page

HttpSource::occurrences()

public function occurrences(OccurrenceQuery $query): Page

HttpSource::datasets()

public function datasets(Query $query): Page

HttpSource::open()

public function open(Asset|string $file): ResponseInterface

JobSourceInterface interface

interface JobSourceInterface extends SourceInterface JobSourceInterface.php:24

A source that makes files on request (Capability::JOBS): the Copernicus Climate and Atmosphere Data Stores cut a dataset to the variables, days and area asked, queue the request, then keep the file for a while. The source never waits: the application asks job() again until the job is done, then reads its file with open().

Beside SourceInterface rather than in it: a source written before kept methods of these names that answered a Product, which a Job extends.

JobSourceInterface::submit()

public function submit(string $dataset, array $request): Job

Submits a request for a file of a dataset; the service queues it.

$request

the dataset's inputs, in its own names

JobSourceInterface::job()

public function job(string $id): ?Job

A request as the service has it now; null when it does not know it (or no longer).

JobSourceInterface::jobs()

public function jobs(ProductQuery $query = new ProductQuery()): Page

The account's requests, a page at a time, newest first unless the query sorts otherwise.

OccurrenceQuery class

final readonly class OccurrenceQuery OccurrenceQuery.php:26

What to ask occurrences() for: the things found or happened at a place in a period - the observations of a species, the earthquakes of a region. The place is a BBox, or a Point and the radius around it in kilometres (null: the source's default, which its README states); no place is anywhere. The period has both ends included, a date without a time standing for its whole day; no end is open.

What only one domain filters on - a taxon, a country, a magnitude - is a filter, by key: the keys below are shared by the sources that read them (a new source reuses one before it makes one), and a source refuses a key it does not read, before any call, rather than answer unfiltered. Each source's README lists its keys and the values they take.

new OccurrenceQuery(new Point(-24.99, 31.59), new \DateTimeImmutable('2025-01-01'), new \DateTimeImmutable('2025-12-31'), [OccurrenceQuery::TAXON => '4CGXP'], radius: 100) new OccurrenceQuery(new BBox(-25, 35, 45, 72), $from, $to, [OccurrenceQuery::MIN_MAGNITUDE => 4], sort: 'magnitude') $query->with(['cursor' => $page->next])

const NEWEST = 'newest'

The latest first.

const OLDEST = 'oldest'

The earliest first.

const TAXON = 'taxon'

A taxon: an Identifier, a key or a scientific name the source reads (GBIF).

const COUNTRY = 'country'

A country, ISO 3166-1 alpha-2 ("ZA"), or a list of them (GBIF).

const KIND = 'kind'

The source's word for what it is, or a list of them: "earthquake", "quarry blast" (USGS); "human_observation", "preserved_specimen" (GBIF's basis of record).

const MIN_MAGNITUDE = 'min_magnitude'

The smallest magnitude, included (USGS).

const MAX_MAGNITUDE = 'max_magnitude'

The largest magnitude, included (USGS).

const LICENSE = 'license'

The licence a record is shared under, or a list of them, as Creative Commons writes it lower case: "cc0", "cc-by", "cc-by-nc" (iNaturalist).

public Point|BBox|null $place
public ?\DateTimeInterface $from
public ?\DateTimeInterface $to
public array $filters

by key: OccurrenceQuery::TAXON, COUNTRY, KIND... or a key of the source's own

public ?float $radius

around a Point, in kilometres; null: the source's default

public int $limit

occurrences per page (each source caps it)

public ?string $cursor

the Page::$next of the page before

public ?string $sort

OccurrenceQuery::NEWEST, OLDEST, or a key the source sorts by ("magnitude"); null: the source's own order

OccurrenceQuery::__construct()

public function __construct(public Point|BBox|null $place = null, public ?\DateTimeInterface $from = null, public ?\DateTimeInterface $to = null, public array $filters = [], public ?float $radius = null, public int $limit = 25, public ?string $cursor = null, public ?string $sort = null)
$filters

by key: OccurrenceQuery::TAXON, COUNTRY, KIND... or a key of the source's own

$radius

around a Point, in kilometres; null: the source's default

$limit

occurrences per page (each source caps it)

$cursor

the Page::$next of the page before

$sort

OccurrenceQuery::NEWEST, OLDEST, or a key the source sorts by ("magnitude"); null: the source's own order

OccurrenceQuery::filter()

public function filter(string $key): mixed

A filter's value; null when the query does not filter on it.

OccurrenceQuery::with()

public function with(array $changes): self

Page class

final readonly class Page implements IteratorAggregate, Countable Page.php:14

One page of entities (search(), occurrences()) or of products (products()). $next is the cursor of the following page (pass it as the query's cursor), null on the last one.

public array $items
public ?string $next
public ?int $total

how many the whole query matches, when the source says

Page::__construct()

public function __construct(public array $items = [], public ?string $next = null, public ?int $total = null)
$total

how many the whole query matches, when the source says

Page::isLast()

public function isLast(): bool

Page::first()

public function first(): mixed

Page::getIterator()

public function getIterator(): \ArrayIterator

Page::count()

public function count(): int

ProductQuery class

final readonly class ProductQuery ProductQuery.php:17

What to ask products() for. Every criterion is optional; a source applies those it understands and refuses $maxCloudCover without Capability::CLOUD_COVER, and a BBox without Capability::AREA, rather than answer unfiltered.

new ProductQuery(['sentinel-2-l2a'], new Point(48.5734, 7.7521), new \DateTimeImmutable('-10 days'), maxCloudCover: 30) $query->with(['cursor' => $page->next])

const NEWEST = 'newest'
const OLDEST = 'oldest'
public array $collections

the source's collection ids ("sentinel-2-l2a"); empty: all

public Point|BBox|null $place
public ?\DateTimeInterface $from
public ?\DateTimeInterface $to
public ?float $maxCloudCover

in percent, included

public int $limit

products per page (each source caps it)

public ?string $cursor

the Page::$next of the page before

public string $sort

ProductQuery::__construct()

public function __construct(public array $collections = [], public Point|BBox|null $place = null, public ?\DateTimeInterface $from = null, public ?\DateTimeInterface $to = null, public ?float $maxCloudCover = null, public int $limit = 25, public ?string $cursor = null, public string $sort = self::NEWEST)
$collections

the source's collection ids ("sentinel-2-l2a"); empty: all

$maxCloudCover

in percent, included

$limit

products per page (each source caps it)

$cursor

the Page::$next of the page before

ProductQuery::with()

public function with(array $changes): self

Query class

final readonly class Query Query.php:16

What to ask search() for: a text, optionally the domain the caller means (a source that spans several - Wikidata - narrows to it; a source of one domain answers nothing for another), a page size and the cursor of the page before.

new Query('caffeine', limit: 10) $query->with(['cursor' => $page->next])

public string $text
public ?Domain $domain
public int $limit

entities per page (each source caps it)

public ?string $cursor

the Page::$next of the page before

Query::__construct()

public function __construct(public string $text, public ?Domain $domain = null, public int $limit = 25, public ?string $cursor = null)
$limit

entities per page (each source caps it)

$cursor

the Page::$next of the page before

Query::with()

public function with(array $changes): self

SeriesQuery class

final readonly class SeriesQuery SeriesQuery.php:25

What to ask series() for: the variables, in the source's names ("temperature_2m", "precipitation"); the place - a Point, a BBox (Capability::AREA) or a station's Identifier (Capability::STATION: its own records, not the nearest one's); the period, both ends included (a date without a time stands for the whole day); the step (SeriesQuery::HOURLY, DAILY, or another ISO 8601 duration the source knows); the aggregation of a step longer than the measure (Series::MAX, MIN, MEAN, SUM); the time zone the times and the days are given in (null: the source's default, which its README states).

new SeriesQuery(['temperature_2m'], new Point(48.5734, 7.7521), new \DateTimeImmutable('tomorrow'), new \DateTimeImmutable('tomorrow')) new SeriesQuery(['temperature_2m'], $strasbourg, $from, $to, SeriesQuery::DAILY, Series::MAX) new SeriesQuery(['TMAX'], Identifier::of(Scheme::GHCN, 'USW00094728'), $from, $to, SeriesQuery::DAILY)

const HOURLY = 'PT1H'
const DAILY = 'P1D'
const STATIONS = [Scheme::GHCN, Scheme::WIGOS, Scheme::NWS_STATION, Scheme::USGS_SITE, Scheme::COOPS_STATION, Scheme::DWD_STATION]

The schemes of a station.

public array $variables
public Point|BBox|Identifier $place
public ?\DateTimeInterface $from
public ?\DateTimeInterface $to
public string $step
public ?string $aggregation
public ?string $timezone

SeriesQuery::__construct()

public function __construct(public array $variables, public Point|BBox|Identifier $place, public ?\DateTimeInterface $from = null, public ?\DateTimeInterface $to = null, public string $step = self::HOURLY, public ?string $aggregation = null, public ?string $timezone = null)

SeriesQuery::with()

public function with(array $changes): self

SourceFactory class

abstract class SourceFactory implements SourceFactoryInterface SourceFactory.php:18

The Omnibus way: a source's factory fills a Config - its name ("omnilab.factory_name"), the options it needs ("omnilab.required_options") and their defaults - then builds the source from it, on the application's HTTP client when one is given.

Options every source takes: base_uri, mailto (a contact address, sent in the User-Agent so that a service can reach the caller before it blocks them), user_agent, throttle (seconds between two calls).

Options

OptionDefault
mailtonull
user_agentnull
throttle0.0

SourceFactory::__construct()

public function __construct(protected readonly ?HttpClientInterface $http = null)

SourceFactory::getName()

public function getName(): string

SourceFactory::create()

public function create(array $options = []): SourceInterface

SourceFactory::createConfig()

public function createConfig(array $options = []): Config

SourceFactoryInterface interface

interface SourceFactoryInterface SourceFactoryInterface.php:6

Builds a source from its options (a contact address, a key, a base URI...).

SourceFactoryInterface::getName()

public function getName(): string

The name sources are configured with: "pubchem", "open-meteo", "copernicus".

..

SourceFactoryInterface::create()

public function create(array $options = []): SourceInterface

SourceInterface interface

interface SourceInterface SourceInterface.php:53

A service that publishes what science knows or measures: PubChem, Open-Meteo, the Copernicus Data Space... whatever its domain. Its answers take one of four shapes:

  • an Entity: one thing, named and identified, with its properties and its identifiers in other bases (entity(), search());
  • a Series: one variable at a place over a time (series());
  • a Product: a catalogued scene or file, its footprint and its assets (products());
  • occurrences: the entities found or happened at a place in a period, each with its time and its place - the observations of a species, the earthquakes of a region (occurrences()).

A catalogue's datasets are Products too (datasets()), and its files are read with the source's credentials (open()); a source that makes files on request implements JobSourceInterface besides.

A source answers what it can, says what that is (capabilities()), and throws NotSupportedException for the rest: an operation it does not do, an identifier of a scheme it cannot read - before any call, so that the Collector can offer a source an identifier without cost.

An entity is named by an Identifier, or by a string the source reads: one of its identifiers ("2519", "CHEBI:27732"), a page of a base ("https://pubchem.ncbi.nlm.nih.gov/compound/2519") or, for the sources that look things up by name, a name ("caffeine").

Unknown is null (or an empty page, a series without points, no series for an unknown station); a service that is down or refuses is an UnavailableException (RateLimitedException when asked to slow down); any other error answer a ProviderException.

SourceInterface::getName()

public function getName(): string

The name it is configured as: "pubchem", "open-meteo", "copernicus".

..

SourceInterface::capabilities()

public function capabilities(): array

SourceInterface::entity()

public function entity(Identifier|string $id): ?Entity

One entity, or null when unknown.

public function search(Query $query): Page

Entities matching a text, one page at a time, the best match first.

SourceInterface::series()

public function series(SeriesQuery $query): array

One series per variable asked, in the order asked: a variable the source has no value for there (no station near the Point, no sensor of it) is a series without points, at the place asked. A station the source does not know (an Identifier it reads, unknown, or without a place to give a series) is an empty list.

SourceInterface::products()

public function products(ProductQuery $query): Page

Products over the place and the period, one page at a time, newest first unless the query sorts otherwise.

SourceInterface::occurrences()

public function occurrences(OccurrenceQuery $query): Page

The things found or happened at the place in the period, one page at a time, each an Entity with its time and its place (a Point); in the source's order unless the query sorts. An entity of a page may hold less than entity() gives for it (its README says what): entity($it->id) reads it whole.

SourceInterface::datasets()

public function datasets(Query $query): Page

The datasets of the source's catalogue that match a text, the best match first, one page at a time: each a Product whose id is what ProductQuery::$collections takes.

SourceInterface::open()

public function open(Asset|string $file): ResponseInterface

A file (an asset, or its address) opened with the source's credentials: the response, streamed, not read. Nothing is decoded.

Stac class

final class Stac Stac.php:20

The reading of a STAC API (stacspec.org, 1.0) that the catalogues share: a ProductQuery written as an item search's parameters, its answer read back into Products, a collection read as a dataset. A source makes the call itself (its getJson(), its throttle, its errors) and gives the answer here.

$data = $this->getJson('search', Stac::parameters($query, $collections, 100, Stac::CQL2) + ['token' => $query->cursor]); return Stac::page($data ?? [], 'copernicus', 'token', $query->limit);

const CQL2 = 'cql2-text'

The cloud cover filtered by the Filter extension, in CQL2 text: filter=eo:cloud_cover<=30 (the Copernicus Data Space).

const QUERY = 'query'

The cloud cover filtered by the Query extension: query[eo:cloud_cover][lte]=30 (NASA's CMR-STAC).

Stac::parameters()

public static function parameters(ProductQuery $query, array $collections, int $maxPerPage, string $cloud = self::CQL2): array

The GET parameters of an item search: the collections, a BBox (bbox) or a Point (intersects), the period in UTC (".." for an open end), the limit capped, newest or oldest first, the cloud cover in the catalogue's dialect (CQL2 or QUERY). Null values are none.

Stac::page()

public static function page(array $answer, string $source, string $cursor, ?int $asked = null): Page

An item search's answer (a FeatureCollection) as a Page: its items, the value of $cursor in its next link (null on the last page, or on a page shorter than asked), its total (numberMatched, or the Context extension's matched).

Stac::item()

public static function item(array $item, string $source): Product

A STAC item as a Product.

Stac::collection()

public static function collection(array $collection, string $source): Product

A STAC collection as a Product: a dataset, its id what an item search's collections take.