Skip to content

Geographic reference data QBit for QQQ - countries, states/provinces, and cities

License

Notifications You must be signed in to change notification settings

QRun-IO/qbit-geo-data

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

AGPL-3.0 - See LICENSE

About

Geographic reference data QBit for QQQ - countries, states/provinces, and cities

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages