# QBit: Geo Data [![Alpha](https://img.shields.io/badge/status-alpha-orange)]() [![CircleCI](https://circleci.com/gh/QRun-IO/qbit-geo-data.svg?style=shield)](https://circleci.com/gh/QRun-IO/qbit-geo-data) Geographic reference data QBit providing countries, states/provinces, and cities for QQQ applications. ## Quick Start Add the dependency to your `pom.xml`: ```xml com.kingsrook.qbits qbit-geo-data-core ${qbit-geo-data.version} ``` ## 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: ```java 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. ```java 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: ```java 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 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 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: ```java 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: ```java // 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: ```java 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](LICENSE)