About Yamaokaya Map (Unofficial)
Do you know about the wonderful ramen chain “Ramen Yamaokaya” in Japan?
Ramen Yamaokaya is a nationwide ramen chain founded in 1988 in Ushiku City, Ibaraki Prefecture, Japan. It's known for its rich tonkotsu broth and for allowing you to freely customize noodle firmness, flavor intensity, and fat content. Many locations are open 24 hours, making it beloved by truck drivers and night shift workers. I myself have been a fan for over 20 years. My home store is the legendary “Minami 2-jo Store” in Sapporo. I always order the shoyu ramen with less fat.
Last year's AWS Summit Japan 2025 inspired me to think about how I could support Yamaokaya within my area of expertise.
So, I built an unofficial web application called “Yamaokaya Map.” This map lets you view store information for Ramen Yamaokaya locations nationwide.
This app supports PWA, so you can add it to your smartphone's home screen.
How to add:
- iOS (Safari): Share button → “Add to Home Screen.”
- Android (Chrome): Menu → “Add to Home Screen.”
Ramen Yamaokaya Store Types
Ramen Yamaokaya has four store types.
1. Ramen Yamaokaya
The standard Yamaokaya. Offers classic tonkotsu-based menu items. There are over 150 locations nationwide. I always order the Shoyu Ramen.
2. Niboshi Ramen Yamaokaya
A specialty shop serving niboshi (dried sardine) broth ramen. You can enjoy a different flavor profile from the standard Yamaokaya. I'm not a fan of niboshi, so I've actually never been.
3. Miso Ramen Yamaokaya
A shop specializing in miso ramen, known for its rich miso soup. Here, I recommend ordering the Shoyu Ramen deliberately. There are only 3 locations, all in Hokkaido.
4. Gyoza no Yamaokaya
A new concept store focusing on gyoza. There's only one location in all of Japan, located in Sapporo.
The map released this time uses icons to distinguish these four store types, and you can toggle their display on or off via layer switching.
Advance Preparation
Contacting the Official Website
Since I was going to perform scraping this time, I checked with the official website beforehand. They gave me a very warm response. Thanks to that, I immediately wanted to go eat there again.
Data Acquisition and Processing
This time, I'll use Python for scraping. I'll combine Playwright, pandas, and geopy to acquire and process the data.
- Scraping: Playwright
- Data Processing: pandas
- DMS→DD Conversion: geopy
yamaokaya-data
└── script
├── scrape_yamaokaya.py
├── latlon_yamaokaya.py
├── column_yamaokaya.py
├── csv2geojson.py
Map Application
First, fork the Amazon Location Service v2 starter template. Then, add the files and code needed for the Yamaokaya Map.
MapLibre GL JS & Amazon Location Service Starter
Execution environment
- node v24.4.1
- npm v11.4.2
yamaokaya-map
├── LICENSE
├── README.md
├── dist
│ └── index.html
├── img
│ ├── README01.gif
│ ├── README02.png
│ └── README03.png
├── index.html
├── package-lock.json
├── package.json
├── public
│ ├── manifest.json
│ ├── data
│ │ ├── yama.geojson
│ │ ├── niboshi.geojson
│ │ ├── miso.geojson
│ │ └── gyouza.geojson
│ └── icons
│ ├── yama.png
│ ├── niboshi.png
│ ├── miso.png
│ └── gyouza.png
├── src
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts
Install the package
npm install
Publishing Settings in Amplify Gen2
Using the starter repository I forked, I’ll publish it on GitHub in the Amplify Console (Gen2), referencing an article I wrote previously.
https://memo.dayjournal.dev/memo/aws-amplify-016
Data Acquisition and Processing
Scraping
The script scrapes store information from the official website. Since the official site dynamically generates content, I use Playwright to control the browser and retrieve the data. From each store's detail page, I extract the store name, address, phone number, business hours, parking information, seat types, shower room availability, the detail page URL, and the store's location information.
Example of retrieving the store name
from playwright.sync_api import sync_playwright
import pandas as pd
def scrape_yamaokaya_shops():
shops = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
)
context.set_default_timeout(10000)
context.set_default_navigation_timeout(10000)
page = context.new_page()
main_url = "https://www.yamaokaya.com/shops/"
page.goto(main_url, wait_until='networkidle', timeout=10000)
page.wait_for_timeout(5000)
shop_links = page.eval_on_selector_all(
'a[href*="/shops/"]',
'els => [...new Set(els.map(el => el.href).filter(href => /shops\\/\\d+/.test(href)))]'
)
for url in shop_links:
try:
page.goto(url, wait_until='domcontentloaded', timeout=10000)
page.wait_for_timeout(5000)
name = page.evaluate("""() => {
const h = document.querySelector('h2, h1, .shop-name');
return h?.innerText?.trim() || document.title.split('|')[0].trim();
}""")
shops.append({'url': url, 'name': name or '不明'})
except Exception as e:
shops.append({'url': url, 'name': 'エラー'})
browser.close()
return pd.DataFrame(shops)
if __name__ == "__main__":
df = scrape_yamaokaya_shops()
df.to_csv('yamaokaya_shops.csv', index=False, encoding='utf-8-sig')
DMS→DD Conversion
The location data scraped is in DMS (degrees, minutes, seconds) format. To display it with the map library, I convert it to DD format (decimal degrees). I use geopy to handle multiple conversion patterns.
Example of DMS→DD conversion
from typing import Tuple
from geopy import Point
# 変換前 "43°03'28.6""N 141°21'22.2""E"
def _convert_with_geopy(dms_string: str) -> Tuple[float, float]:
cleaned = dms_string.replace('""', '"')
point = Point(cleaned)
return point.latitude, point.longitude
Column Name Change
Before converting the data to GeoJSON, I change Japanese column names to English.
Example of column name change
column_mapping = {
'店舗名': 'store_name',
'住所': 'address',
'電話番号': 'phone_number',
'営業時間': 'business_hours',
'駐車場': 'parking',
'座席の種類': 'seating_types',
'シャワー室': 'shower_room',
'その他': 'other_info'
}
df_renamed = df.rename(columns=column_mapping)
CSV to GeoJSON Conversion
Finally, I convert the CSV to GeoJSON format. Files are output separately for each store type.
Example of CSV to GeoJSON conversion
import json
import pandas as pd
def create_geojson_features(df):
features = []
for _, row in df.iterrows():
properties = {}
for col in df.columns:
if col not in ['lat', 'lon']:
value = row[col]
if pd.isna(value):
properties[col] = None
else:
properties[col] = str(value)
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [row['lon'], row['lat']]
},
"properties": properties
}
features.append(feature)
return features
GeoJSON output result
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
141.3561,
43.0579
]},
"properties": {
"store_name": "ラーメン山岡家 南2条店",
"details": "https://www.yamaokaya.com/shops/1102/",
"address": "札幌市中央区南2条西1丁目6-1",
"phone_number": "(011) 242-4636",
"business_hours": "5:00-翌4:00",
"parking": "なし",
"seating_types": "カウンター席: 13",
"shower_room": "なし",
"other_info": "まちなかのちいさなお店です。"
}
},
Creating the Map Application
Setting the Background Map
For this project, I use MapLibre GL JS as the map library and Amazon Location Service for the background map.
import './style.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import 'maplibre-gl-opacity/dist/maplibre-gl-opacity.css';
import maplibregl from 'maplibre-gl';
import OpacityControl from 'maplibre-gl-opacity';
const region = import.meta.env.VITE_REGION;
const mapApiKey = import.meta.env.VITE_MAP_API_KEY;
const mapName = import.meta.env.VITE_MAP_NAME;
const map = new maplibregl.Map({
container: 'map',
style: `https://maps.geo.${region}.amazonaws.com/maps/v0/maps/${mapName}/style-descriptor?key=${mapApiKey}`,
center: [138.0000, 38.5000],
zoom: baseZoom,
maxZoom: 20
});
Layer Configuration
I set up layers for each store type and assign custom icons to them.
interface LayerConfig {
name: string;
iconPath: string;
iconId: string;
visible: boolean;
}
const layerConfigs: Record<string, LayerConfig> = {
'gyouza': {
name: '餃子の山岡家',
iconPath: 'icons/gyouza.png',
iconId: 'gyouza-icon',
visible: true
},
'miso': {
name: '味噌ラーメン山岡家',
iconPath: 'icons/miso.png',
iconId: 'miso-icon',
visible: true
},
'niboshi': {
name: '煮干しラーメン山岡家',
iconPath: 'icons/niboshi.png',
iconId: 'niboshi-icon',
visible: true
},
'yama': {
name: 'ラーメン山岡家',
iconPath: 'icons/yama.png',
iconId: 'yama-icon',
visible: true
}
};
Adding GeoJSON Layers
I add the GeoJSON data as layers. I configure the icon size to change based on the zoom level.
function addGeoJsonLayer(id: string, config: LayerConfig, data: GeoJSONData): void {
map.addSource(id, {
type: 'geojson',
data: data
});
map.addLayer({
id: id,
type: 'symbol',
source: id,
layout: {
'icon-image': config.iconId,
'icon-size': [
'interpolate',
['linear'],
['zoom'],
6, baseIconSize * 0.5,
10, baseIconSize * 0.6,
14, baseIconSize * 0.7,
18, baseIconSize * 0.8
],
'icon-allow-overlap': true,
'icon-ignore-placement': false,
},
paint: {
'icon-opacity': 1.0,
}
});
}
Implementing Popups
Clicking a store icon displays the store information in a popup. It shows the address, phone number, business hours, parking information, seating types, etc.
function createPopupContent(props: StoreProperties): string {
const contentParts: string[] = [];
if (props.store_name) {
contentParts.push(`<h3>${props.store_name}</h3>`);
}
const details: string[] = [];
if (props.address) {
details.push(`<strong>住所:</strong> ${props.address}`);
}
if (props.phone_number) {
details.push(`<strong>電話:</strong> <a href="tel:${props.phone_number}">${props.phone_number}</a>`);
}
// ...
}
Layer Toggling
Implemented layer toggling (show/hide) using maplibre-gl-opacity.
const overLayers = {
'yama': 'ラーメン山岡家',
'niboshi': '煮干しラーメン山岡家',
'miso': '味噌ラーメン山岡家',
'gyouza': '餃子の山岡家',
};
const opacityControl = new OpacityControl({
overLayers: overLayers,
opacityControl: false
});
map.addControl(opacityControl, 'bottom-left');
Summary
This time, I built the "Yamaokaya Map (Unofficial)" using a structure that includes Playwright for scraping, geopy for DMS→DD conversion and CSV→GeoJSON conversion, and map display via MapLibre GL JS and Amazon Location Service. Visualizing this on a map reveals new insights. The northernmost store is in Wakkanai. Stores are located in surrounding areas rather than central Tokyo. While they have expanded into Kyushu, there are no stores in Shikoku. And there is only one Gyoza no Yamaokaya store nationwide. This way, Ramen Yamaokaya's store opening strategy becomes clear.
Please use this when searching for a nearby store or looking for Ramen Yamaokaya while traveling!





Top comments (0)