This solution leverages backend automation and scheduled orchestration to process business addresses and generate static maps at scale.
A client-focused tool that automates the collection and enrichment of business address data using modern backend technologies and scheduled job orchestration.
This tool streamlines business address discovery and enrichment for marketing, analysis, or integration with client workflows—demonstrating scalable backend automation.
This solution uses Spring Batch to orchestrate the enrichment and transformation of business addresses into downloadable map assets. Below are the building blocks and code examples that make this system scalable and maintainable.
Purpose: Defines the orchestration and flow of steps.
return jobBuilderFactory.get("addressJob")
.start(partitionedStep())
.next(finalizeStep())
.build();RangePartitioner dynamically divides work based on the job quota and last processed ID:
int chunkSize = quota / gridSize + (quota % gridSize > 0 ? 1 : 0);
for (int i = 0; i < gridSize; i++) {
int fromId = lastProcessedKey + (i * chunkSize) + 1;
int toId = Math.min(lastProcessedKey + ((i + 1) * chunkSize), lastProcessedKey + quota);
context.put("fromId", fromId);
context.put("toId", toId);
}Enables parallelized steps to fetch and process business entries in defined ID ranges.
Purpose: Reads business records by ID range.
JdbcPagingItemReader<StaticMapStore> reader = new JdbcPagingItemReader<>();
reader.setQueryProvider(...);In StaticMapItemProcessor, a latitude and longitude pair is translated into a map image request:
GoogleInfo meta = new GoogleMetadataBuilder()
.setAccessKey(accessKey)
.setGoogleServiceEndpoint(serviceEndpoint)
.setPlaceholder(Map.of("{longitude}", store.getLng(), "{latitude}", store.getLat()))
.execute();
ImageInfo imageInfo = new ImageInfo();
imageInfo.setImageName(prefix + store.getStoreId() + suffix);
item.addMetadata("GoogleInfo", meta);
item.addMetadata("ImageInfo", imageInfo);Prepares metadata to fetch and label map images per store.
StaticMapWriter delegates to modular task components:
googleServiceFactory.getTask("GoogleDownloadTask")
.execute(googleInfo, imageInfo);
googleServiceFactory.getTask("S3UploadTask")
.execute(s3Info, imageInfo);Enables separation of download and upload logic.
Purpose: Makes job reusable with dynamic input.
--job.name=addressJob lastProcessedKey=500 quota=1000Purpose: Ensures recoverability from partial failures.
<chunk skip-limit="50">
<skippable-exception-classes>
<include class="SkippableException"/>
</skippable-exception-classes>
</chunk>Purpose: Automates and schedules job execution.
java -jar batch-job.jar --job.name=addressJob quota=500Purpose: Tracks success, failure, and job metrics.
public void afterStep(StepExecution step) {
log.info("Success: " + jobState.getSuccess());
}Explore key components of the github.com/khimu/api Sample Code Snippet
ScrapperJob.java@Bean
public Job scrapperJob() {
return jobBuilderFactory.get("scrapperJob")
.start(scrapperStep())
.build();
}MainJob.java@Bean
public Job mainJob() {
return jobBuilderFactory.get("mainJob")
.start(stepOne())
.next(stepTwo())
.build();
}S3UploadTask.javapublic void uploadToS3(String filePath) {
s3Client.putObject(bucketName, keyName, new File(filePath));
}GoogleGeocodeTask.javaString url = "https://maps.googleapis.com/maps/api/geocode/json?address=" + address + "&key=" + apiKey;
HttpResponse response = httpClient.execute(new HttpGet(url));StoreKeywordItemReader.javapublic StoreKeyword read() {
return jdbcTemplate.queryForObject("SELECT * FROM store_keywords WHERE ...", new StoreKeywordRowMapper());
}StaticMapWriter.javapublic void write(List<? extends StaticMap> items) {
for (StaticMap map : items) {
s3UploadTask.upload(map.getImagePath());
}
}RangePartitioner.javapublic Map<String, ExecutionContext> partition(int gridSize) {
Map<String, ExecutionContext> result = new HashMap<>();
int range = endId - startId + 1;
int chunkSize = range / gridSize;
for (int i = 0; i < gridSize; i++) {
ExecutionContext context = new ExecutionContext();
context.putInt("startId", startId + (i * chunkSize));
context.putInt("endId", (i == gridSize - 1) ? endId : startId + ((i + 1) * chunkSize) - 1);
result.put("partition" + i, context);
}
return result;
}These APIs enable the transformation and visualization of location data within the tool.
📍
https://maps.googleapis.com/maps/api/geocode/json?address={address}
Converts an address into geographic coordinates.
🔍
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location={latitude},{longitude}&radius={radius}
Finds nearby businesses based on location and radius.
🗺️
http://maps.googleapis.com/maps/api/staticmap?center={latitude},{longitude}&zoom=15&size=400x125
Generates map images for preview and storage.
Explore key components of the github.com/khimu/api Sample Code Snippet
(Code listing section in progress)