Lue dans les fichiers PHP du paquet au commit cd8f6e2398d0 : ses espaces de noms, classes, interfaces et énumérations, leurs constantes, propriétés et méthodes publiques avec leur PHPDoc, et les options que prennent ses fabriques. Rien n’en est exécuté.
CollectorclassOne 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
ConfigclassA 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
MergerclassThe 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
RegistryclassThe sources by name, each built once from its factory and options:
7 methods
OmnilabBundleclassOmnilab 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
InvalidConfigExceptionclassA source misconfigured: a key missing, an unknown factory.
NotSupportedExceptionclassThe source does not read that kind of identifier, or does not do that at all.
2 methods
OmnilabExceptioninterfaceEvery exception Omnilab throws.
ProviderExceptionclassThe source's service answered with an error.
1 method · 2 properties
RateLimitedExceptionclassThe 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
UnavailableExceptionclassThe service could not be reached, or is down: never "not found". A caller that caches an answer must not cache this one.
RedactionclassWhat 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
AssetclassOne 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
BBoxclassAn 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
DomainenumWhat 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
EntityclassA 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
IdentifierclassOne 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
IdentifiersclassAn entity's identifiers, each once, in the order they were learnt.
14 methods
JobclassA 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
PointclassA place on the Earth: WGS 84 latitude and longitude in degrees, the elevation in metres above sea level when known.
5 methods · 3 properties
ProductclassA 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
PropertyclassOne 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
QuantityclassA 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
SchemeenumThe 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
SeriesclassOne 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
CapabilityenumWhat a source can be asked, and what its answers hold.
15 cases
HttpSourceclassWhat 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
JobSourceInterfaceinterfaceA 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
OccurrenceQueryclassWhat 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
PageclassOne 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
ProductQueryclassWhat 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
QueryclassWhat 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
SeriesQueryclassWhat 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
SourceFactoryclassThe 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
SourceFactoryInterfaceinterfaceBuilds a source from its options (a contact address, a key, a base URI...).
2 methods
SourceInterfaceinterfaceA 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
StacclassThe 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): ?EntityThe entity a source gives, completed by the others; null when that source does not know it.
$sourcesthe configured sources to follow into; null: all
Collector::follow()
public function follow(Entity $entity, int $depth = 1, ?array $sources = null): EntityAn entity completed by the sources that read its cross-references.
$sourcesthe configured sources to follow into; null: all
Collector::search()
public function search(Query $query, ?array $sources = null): arrayEvery source that searches, asked the same query, their answers merged.
$sourcesthe 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): selfSets each key not set yet (a null counts as not set).
Config::validateNotEmpty()
public function validateNotEmpty(array $keys): voidConfig::get()
public function get(string $key, mixed $default = null): mixedMerger 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 = [])$preferencesfield or property key => sources, "default" for the others
Merger::merge()
public function merge(iterable ...$lists): arrayEach 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): boolWhether the Merger takes two entities for the same thing.
Merger::combine()
public function combine(Entity ...$entities): EntityOne 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): SourceInterfaceRegistry::has()
public function has(string $name): boolRegistry::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): arrayThe 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): voidOmnilabBundle::loadExtension()
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): voidOmnilab\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'): selfNotSupportedException::operation()
public static function operation(string $source, string $operation): selfOmnilabException 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 $sourcepublic 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 = []): arrayThe 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.
$optionsthe HTTP client's options
Redaction::sensitive()
public static function sensitive(string $name): boolWhether a parameter, header or field of this name holds a credential.
Redaction::text()
public static function text(string $text, array $secrets = []): stringA 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 = []): \ThrowableA 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): \ThrowableThe 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 $namepublic string $hrefpublic ?string $typepublic array $rolespublic ?string $titlepublic ?int $sizepublic 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): boolAsset::httpsHref()
public function httpsHref(): ?stringThe 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 $westpublic float $southpublic float $eastpublic 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$bboxa GeoJSON bbox (a 3D one's elevations dropped)
BBox::contains()
public function contains(Point $point): boolBBox::center()
public function center(): PointBBox::toArray()
public function toArray(): array- Returns
west, south, east, north
BBox::toGeoJson()
public function toGeoJson(): arrayAs a GeoJSON polygon.
BBox::__toString()
public function __toString(): stringDomain 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(): stringEntity 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 $idpublic string $labelpublic Domain $domainpublic ?string $kindthe source's word for what it is: "compound", "protein", "species", "galaxy", "earthquake"
public array $synonymsthe other names it goes by
public ?string $descriptionpublic array $propertiespublic Identifiers $identifierspublic ?string $urlpublic ?string $imagepublic ?\DateTimeImmutable $timepublic Point|BBox|null $placepublic ?string $sourcepublic array $sourcesthe 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 = [])$kindthe source's word for what it is: "compound", "protein", "species", "galaxy", "earthquake"
$synonymsthe other names it goes by
$sourcesthe sources merged into this record
$relatedother things it is tied to, by relation ("organism", "structure", "ligand"...)
Entity::with()
public function with(array $changes): selfA copy with some fields changed: $entity->with(['description' => $text]).
Entity::identifiers()
public function identifiers(): IdentifiersEvery identifier, its own included, each once.
Entity::identifier()
public function identifier(Scheme $scheme): ?stringThe first value of a scheme: $compound->identifier(Scheme::CAS) is "58-08-2".
Entity::related()
public function related(string $relation): IdentifiersThe 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): ?PropertyThe 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|nullA 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): ?selfThe identifier, or null when the value is not one of that scheme.
Identifier::parse()
public static function parse(string $input): ?selfReads 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): ?SchemeThe scheme a name or a prefix stands for: "chebi", "pubchem.cid", "ncbigene", "CHEBI".
..
Identifier::cid()
public static function cid(int|string $value): selfIdentifier::chebi()
public static function chebi(int|string $value): selfIdentifier::inchikey()
public static function inchikey(string $value): selfIdentifier::cas()
public static function cas(string $value): selfIdentifier::wikidata()
public static function wikidata(string $value): selfIdentifier::key()
public function key(): string"chebi:27732": what the Merger and the collections key on.
Identifier::display()
public function display(): stringThe identifier as its base writes it: CHEBI:27732, CID 2519, 58-08-2.
Identifier::equals()
public function equals(self $other): boolIdentifier::url()
public function url(): ?stringThe page of the base that holds it, when there is one.
Identifier::__toString()
public function __toString(): stringIdentifier::ensemblSpecies()
public static function ensemblSpecies(self|string $id): ?stringThe 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 = [])$identifiersnulls (an identifier that did not parse) are skipped
Identifiers::of()
public static function of(?Identifier ...$identifiers): selfIdentifiers::get()
public function get(Scheme $scheme): ?IdentifierThe first identifier of that scheme.
Identifiers::value()
public function value(Scheme $scheme): ?stringThe 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): boolIdentifiers::with()
public function with(?Identifier ...$identifiers): selfIdentifiers::merge()
public function merge(self $other): selfIdentifiers::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$arraywhat toArray() gave
Identifiers::getIterator()
public function getIterator(): \ArrayIteratorIdentifiers::count()
public function count(): intJob 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 $statuspublic ?float $progresspercent, 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)$datasetthe dataset the file is made of (the Product's collection)
$progresspercent, when the service says
$propertiesthe service's own (the request, a checksum...)
Job::isDone()
public function isDone(): boolEnded, well or not: successful, failed, rejected or dismissed.
Job::isSuccessful()
public function isSuccessful(): boolJob::file()
public function file(): ?AssetThe 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 $latitudepublic float $longitudepublic ?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): BBoxThe box of the given half-size around it, in kilometres (approximate away from the poles).
Point::toGeoJson()
public function toGeoJson(): arrayAs GeoJSON: longitude first.
Point::__toString()
public function __toString(): stringProduct 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 $idpublic string $collectionpublic ?\DateTimeImmutable $datetimepublic ?\DateTimeImmutable $startpublic ?\DateTimeImmutable $endpublic ?BBox $bboxpublic ?array $geometryGeoJSON
public array $propertiespublic array $assetspublic ?string $previewpublic ?string $urlpublic ?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)$geometryGeoJSON
Product::cloudCover()
public function cloudCover(): ?floatThe 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): ?AssetProduct::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 $keypublic string $labelpublic Quantity|string|int|float|bool $valuepublic ?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|nullThe number, a Quantity's included; null for a text or a boolean.
Property::unit()
public function unit(): ?stringA Quantity's unit; null otherwise.
Property::with()
public function with(array $changes): selfProperty::__toString()
public function __toString(): stringThe 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 $valuepublic ?string $unitpublic ?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): selfThe same number in another unit, when the caller knows the factor: $metres->scaled(0.001, 'km').
Quantity::__toString()
public function __toString(): stringThe 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(): stringScheme::domain()
public function domain(): ?DomainThe 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 $variablepublic ?string $unitpublic Point|BBox $placepublic array $pointspublic ?string $steppublic ?string $aggregationpublic ?string $labelpublic ?bool $forecasttrue 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)$forecasttrue when the points are a forecast, false when observed or reanalysed, null when mixed or unknown
Series::values()
public function values(): arraySeries::times()
public function times(): arraySeries::at()
public function at(\DateTimeInterface $time): int|float|nullThe value at that exact time; null when there is no point then, or no value.
Series::min()
public function min(): int|float|nullSeries::max()
public function max(): int|float|nullSeries::mean()
public function mean(): ?floatSeries::between()
public function between(\DateTimeInterface $from, \DateTimeInterface $to): selfThe points from $from (included) to $to (excluded).
Series::getIterator()
public function getIterator(): \ArrayIteratorSeries::count()
public function count(): intOmnilab\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).
case SEARCH = 'search'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)$headerssent with every call (User-Agent...)
$throttleseconds to leave between two calls
HttpSource::entity()
public function entity(Identifier|string $id): ?EntityHttpSource::search()
public function search(Query $query): PageHttpSource::series()
public function series(SeriesQuery $query): arrayHttpSource::products()
public function products(ProductQuery $query): PageHttpSource::occurrences()
public function occurrences(OccurrenceQuery $query): PageHttpSource::datasets()
public function datasets(Query $query): PageHttpSource::open()
public function open(Asset|string $file): ResponseInterfaceJobSourceInterface 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): JobSubmits a request for a file of a dataset; the service queues it.
$requestthe dataset's inputs, in its own names
JobSourceInterface::job()
public function job(string $id): ?JobA 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()): PageThe 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 $placepublic ?\DateTimeInterface $frompublic ?\DateTimeInterface $topublic array $filtersby key: OccurrenceQuery::TAXON, COUNTRY, KIND... or a key of the source's own
public ?float $radiusaround a Point, in kilometres; null: the source's default
public int $limitoccurrences per page (each source caps it)
public ?string $cursorthe Page::$next of the page before
public ?string $sortOccurrenceQuery::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)$filtersby key: OccurrenceQuery::TAXON, COUNTRY, KIND... or a key of the source's own
$radiusaround a Point, in kilometres; null: the source's default
$limitoccurrences per page (each source caps it)
$cursorthe Page::$next of the page before
$sortOccurrenceQuery::NEWEST, OLDEST, or a key the source sorts by ("magnitude"); null: the source's own order
OccurrenceQuery::filter()
public function filter(string $key): mixedA filter's value; null when the query does not filter on it.
OccurrenceQuery::with()
public function with(array $changes): selfPage 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 $itemspublic ?string $nextpublic ?int $totalhow 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)$totalhow many the whole query matches, when the source says
Page::isLast()
public function isLast(): boolPage::first()
public function first(): mixedPage::getIterator()
public function getIterator(): \ArrayIteratorPage::count()
public function count(): intProductQuery 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 $collectionsthe source's collection ids ("sentinel-2-l2a"); empty: all
public Point|BBox|null $placepublic ?\DateTimeInterface $frompublic ?\DateTimeInterface $topublic ?float $maxCloudCoverin percent, included
public int $limitproducts per page (each source caps it)
public ?string $cursorthe 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)$collectionsthe source's collection ids ("sentinel-2-l2a"); empty: all
$maxCloudCoverin percent, included
$limitproducts per page (each source caps it)
$cursorthe Page::$next of the page before
ProductQuery::with()
public function with(array $changes): selfQuery 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 $textpublic ?Domain $domainpublic int $limitentities per page (each source caps it)
public ?string $cursorthe 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)$limitentities per page (each source caps it)
$cursorthe Page::$next of the page before
Query::with()
public function with(array $changes): selfSeriesQuery 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 $variablespublic Point|BBox|Identifier $placepublic ?\DateTimeInterface $frompublic ?\DateTimeInterface $topublic string $steppublic ?string $aggregationpublic ?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): selfSourceFactory 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
| Option | Default | |
|---|---|---|
mailto | null | |
user_agent | null | |
throttle | 0.0 |
SourceFactory::__construct()
public function __construct(protected readonly ?HttpClientInterface $http = null)SourceFactory::getName()
public function getName(): stringSourceFactory::create()
public function create(array $options = []): SourceInterfaceSourceFactory::createConfig()
public function createConfig(array $options = []): ConfigSourceFactoryInterface 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(): stringThe name sources are configured with: "pubchem", "open-meteo", "copernicus".
..
SourceFactoryInterface::create()
public function create(array $options = []): SourceInterfaceSourceInterface 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(): stringThe name it is configured as: "pubchem", "open-meteo", "copernicus".
..
SourceInterface::capabilities()
public function capabilities(): arraySourceInterface::entity()
public function entity(Identifier|string $id): ?EntityOne entity, or null when unknown.
SourceInterface::search()
public function search(Query $query): PageEntities matching a text, one page at a time, the best match first.
SourceInterface::series()
public function series(SeriesQuery $query): arrayOne 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): PageProducts 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): PageThe 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): PageThe 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): ResponseInterfaceA 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): arrayThe 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): PageAn 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): ProductA STAC item as a Product.
Stac::collection()
public static function collection(array $collection, string $source): ProductA STAC collection as a Product: a dataset, its id what an item search's collections take.