QBit: Geo Data

Alpha CircleCI

Geographic reference data QBit providing countries, states/provinces, and cities for QQQ applications.

Quick Start

Add the dependency to your pom.xml:

<dependency>
   <groupId>com.kingsrook.qbits</groupId>
   <artifactId>qbit-geo-data-core</artifactId>
   <version>${qbit-geo-data.version}</version>
</dependency>

What You Get

This QBit provides three entities with pre-populated reference data:

Entity Fields Records
Country alpha2Code, alpha3Code, numericCode, name, officialName 250
StateProvince countryId, code, name, subdivisionType 5,296
City countryId, stateProvinceId, name, population, latitude, longitude 32,000+

Using this QBit

Step 1: Register the QBit

In your application's metadata setup, register the QBit with a table prefix:

import com.kingsrook.qbits.geodata.GeoDataQBitConfig;
import com.kingsrook.qbits.geodata.GeoDataQBitProducer;

new GeoDataQBitProducer()
   .withConfig(new GeoDataQBitConfig()
      .withBackendName("rdbms")
      .withTableNamePrefix("shipping"))
   .produce(qInstance, "shipping-geo");

This creates three tables in your database:

  • shipping_country
  • shipping_stateProvince
  • shipping_city

Step 2: Run the Sync Process

The QBit includes a sync process that populates tables from bundled JSON data. Run it once after creating your tables, or schedule it to pick up updates.

import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qbits.geodata.sync.GeoDataSyncProcessMetaDataProducer;
import com.kingsrook.qbits.geodata.sync.GeoDataSyncStep;

RunProcessInput input = new RunProcessInput();
input.setProcessName(GeoDataSyncProcessMetaDataProducer.NAME);
input.addValue(GeoDataSyncStep.FIELD_TABLE_NAME_PREFIX, "shipping");

new RunProcessAction().execute(input);

This syncs all three tables (countries, states, cities) in order.

Step 3: Query the Data

Use standard QQQ actions with the prefixed table names and the entity classes:

import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qbits.geodata.model.City;
import com.kingsrook.qbits.geodata.model.Country;
import com.kingsrook.qbits.geodata.model.StateProvince;

import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.*;

// Get a country by alpha-2 code
Country usa = new GetAction().executeForRecord(new GetInput("shipping_country")
   .withUniqueKey(Map.of("alpha2Code", "US")), Country.class);

// Query states for a country
List<StateProvince> states = new QueryAction().execute(new QueryInput("shipping_stateProvince")
   .withFilter(new QQueryFilter(new QFilterCriteria("countryId", EQUALS, usa.getId()))))
   .getRecordEntities(StateProvince.class);

// Query cities with population filter
List<City> cities = new QueryAction().execute(new QueryInput("shipping_city")
   .withFilter(new QQueryFilter()
      .withCriteria(new QFilterCriteria("stateProvinceId", EQUALS, state.getId()))
      .withCriteria(new QFilterCriteria("population", GREATER_THAN, 100000))))
   .getRecordEntities(City.class);

Key concept: The table name (e.g., "shipping_country") includes your prefix. The entity class (Country.class) is always the same regardless of prefix.

Step 4: Use in Dropdowns

Each entity is registered as a PossibleValueSource for use in form dropdowns:

import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;

new QFieldMetaData("countryId", QFieldType.INTEGER)
   .withPossibleValueSourceName("shipping_country")

Multiple Instances

Need geo data for different contexts? Register the QBit multiple times with different prefixes:

// For shipping addresses
new GeoDataQBitProducer()
   .withConfig(new GeoDataQBitConfig()
      .withBackendName("rdbms")
      .withTableNamePrefix("shipping"))
   .produce(qInstance, "shipping-geo");

// For billing addresses
new GeoDataQBitProducer()
   .withConfig(new GeoDataQBitConfig()
      .withBackendName("rdbms")
      .withTableNamePrefix("billing"))
   .produce(qInstance, "billing-geo");

This creates two independent sets of tables:

  • shipping_country, shipping_stateProvince, shipping_city
  • billing_country, billing_stateProvince, billing_city

Query each using its prefixed table name, same entity classes.

Database Schema

Generate a Liquibase changelog for your database:

import com.kingsrook.qbits.geodata.liquibase.GeoDataLiquibaseGenerator;

GeoDataQBitConfig config = new GeoDataQBitConfig()
   .withTableNamePrefix("shipping")
   .withEnableCountries(true)
   .withEnableStateProvinces(true)
   .withEnableCities(true);

GeoDataLiquibaseGenerator.generate(config, Path.of("db/changelog-geo-data.xml"));

The generator creates a changelog with your prefix substituted and only includes tables you've enabled.

License

Both Maven child modules set qbit.noticesDirectory to their parent directory, where this repository's LICENSE and NOTICE live. This supports the shared notice packaging correction in qbit-build-parent; it takes effect after adopting a parent version containing that correction. Existing public parent 2.0.0 is unchanged. Verify each generated module archive against these root files when integrating the new parent.

Apache-2.0 - See LICENSE

S
Description
Geographic reference data QBit for QQQ - countries, states/provinces, and cities
Readme Apache-2.0
6 MiB
0 Stars 3 Watchers 0 Forks
Release 0.1.0
Latest
2026-10-20 23:46:43 +00:00
Languages
Java 100%