Function bodies 121 total
extractSignificantTokens function · typescript · L94-L96 (3 LOC)src/lib/scraper/lapcenter.ts
function extractSignificantTokens(normalizedName: string): string[] {
return normalizedName.split(/\s+/).filter((t) => t.length >= 3 && !isStopRelated(t));
}coreString function · typescript · L98-L104 (7 LOC)src/lib/scraper/lapcenter.ts
function coreString(normalizedName: string): string {
let s = normalizedName.replace(/\s+/g, "");
for (const sw of STOP_WORDS) {
s = s.replaceAll(sw, "");
}
return s;
}fuzzyMatch function · typescript · L106-L161 (56 LOC)src/lib/scraper/lapcenter.ts
export function fuzzyMatch(name1: string, name2: string): boolean {
const norm1 = normalize(name1);
const norm2 = normalize(name2);
const full1 = norm1.replace(/\s+/g, "");
const full2 = norm2.replace(/\s+/g, "");
if (!full1 || !full2) return false;
if (full1 === full2) return true;
const shorter = full1.length <= full2.length ? full1 : full2;
const longer = full1.length <= full2.length ? full2 : full1;
if (shorter.length >= 4 && longer.includes(shorter)) return true;
const core1 = coreString(norm1);
const core2 = coreString(norm2);
if (core1.length >= 3 && core2.length >= 3) {
if (core1 === core2) return true;
const cShorter = core1.length <= core2.length ? core1 : core2;
const cLonger = core1.length <= core2.length ? core2 : core1;
if (cShorter.length >= 4 && cLonger.includes(cShorter)) return true;
}
const tokens1 = extractSignificantTokens(norm1);
const tokens2 = extractSignificantTokens(norm2);
if (tokens1.length > 0 && tokens2matchLapCenterEvents function · typescript · L177-L249 (73 LOC)src/lib/scraper/lapcenter.ts
export async function matchLapCenterEvents(
joeEvents: Array<{
joe_event_id: number;
name: string;
date: string;
lapcenter_event_id?: number;
lapcenter_url?: string;
[key: string]: unknown;
}>
): Promise<MatchResult> {
// Determine which years to fetch
const now = new Date();
const currentYear = now.getFullYear();
const years = [currentYear];
// Also fetch previous year if we're in the first month or have old events
if (now.getMonth() === 0) years.unshift(currentYear - 1);
// Also include previous year if events span that range
const minEventYear = Math.min(...joeEvents.map((e) => parseInt(e.date.slice(0, 4), 10)).filter(Boolean));
if (minEventYear < currentYear && !years.includes(minEventYear)) {
years.unshift(minEventYear);
}
// Fetch Lap Center events
const lcEvents: LapCenterEvent[] = [];
for (const year of years) {
const events = await fetchLapCenterEvents(year);
lcEvents.push(...events);
}
// Group LC events scrapeRanking function · typescript · L58-L70 (13 LOC)src/lib/scraper/rankings.ts
export async function scrapeRanking(
typeId: number,
classId: number
): Promise<JOERankingEntry[]> {
const url = `${BASE_URL}/${typeId}/${classId}`;
const res = await fetch(url, {
headers: { "User-Agent": "trails.jp/1.0 (ranking sync)" },
next: { revalidate: 0 },
});
if (!res.ok) return [];
const html = await res.text();
return parseRankingTable(html);
}parseRankingTable function · typescript · L72-L111 (40 LOC)src/lib/scraper/rankings.ts
function parseRankingTable(html: string): JOERankingEntry[] {
const $ = cheerio.load(html);
const entries: JOERankingEntry[] = [];
const eventHeaders: string[] = [];
$("table thead th, table tr:first-child th").each((i, th) => {
if (i > 3) eventHeaders.push($(th).text().trim());
});
$("table tbody tr").each((_, row) => {
const $row = $(row);
const cells = $row.find("td");
if (cells.length < 4) return;
const rank = parseInt(cells.eq(0).text().trim(), 10);
if (isNaN(rank)) return;
const athlete_name = cells.eq(1).text().trim();
if (!athlete_name) return;
const club = cells.eq(2).text().trim();
const total_points = parseFloat(cells.eq(3).text().trim()) || 0;
const rowClass = $row.attr("class") ?? "";
const is_active = !rowClass.includes("out_ranker");
const event_scores: { event_name: string; points: number }[] = [];
cells.each((i, cell) => {
if (i > 3 && eventHeaders[i - 4]) {
const pts = parseFloat($scrapeAllRankings function · typescript · L116-L140 (25 LOC)src/lib/scraper/rankings.ts
export async function scrapeAllRankings(): Promise<JOERanking[]> {
const results: JOERanking[] = [];
const now = new Date().toISOString();
for (const config of RANKING_CONFIGS) {
for (const cls of config.classes) {
try {
const entries = await scrapeRanking(config.typeId, cls.id);
if (entries.length > 0) {
results.push({
ranking_type: config.type,
class_name: cls.name,
entries,
synced_at: now,
});
}
await new Promise((r) => setTimeout(r, 1200));
} catch (e) {
console.error(`Failed to scrape ${config.type}/${cls.name}:`, e);
}
}
}
return results;
}Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
pixelToGeo function · typescript · L33-L39 (7 LOC)src/lib/tracking/detect-controls.ts
export function pixelToGeo(
px: number,
py: number,
imgWidth: number,
imgHeight: number,
bounds: ImageBounds
): { lat: number; lng: number } {rgbToHsl function · typescript · L51-L70 (20 LOC)src/lib/tracking/detect-controls.ts
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const l = (max + min) / 2;
if (max === min) return [0, 0, l];
const d = max - min;
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
let h = 0;
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
else if (max === g) h = ((b - r) / d + 2) / 6;
else h = ((r - g) / d + 4) / 6;
return [h * 360, s, l];
}isPurplePixel function · typescript · L77-L90 (14 LOC)src/lib/tracking/detect-controls.ts
function isPurplePixel(r: number, g: number, b: number): boolean {
const [h, s, l] = rgbToHsl(r, g, b);
// 紫~マゼンタ色相: 260°-340°
const hueMatch = h >= 260 && h <= 340;
// 十分な彩度 (灰色を除外)
const satMatch = s >= 0.2;
// 明度: 暗すぎず明るすぎず
const lumMatch = l >= 0.15 && l <= 0.85;
// 赤と青が緑より強い (紫の特徴)
const channelMatch = r > g * 0.8 && b > g * 0.8;
return hueMatch && satMatch && lumMatch && channelMatch;
}clusterPixels function · typescript · L95-L100 (6 LOC)src/lib/tracking/detect-controls.ts
function clusterPixels(
mask: Uint8Array,
width: number,
height: number,
cellSize: number
): { cx: number; cy: number; count: number }[] {mergeClusters function · typescript · L133-L136 (4 LOC)src/lib/tracking/detect-controls.ts
function mergeClusters(
clusters: { cx: number; cy: number; count: number }[],
mergeRadius: number
): { cx: number; cy: number; count: number }[] {detectControls function · typescript · L171-L273 (103 LOC)src/lib/tracking/detect-controls.ts
export function detectControls(
imageData: ImageData,
minRadius: number = 8,
maxRadius: number = 60
): DetectedControl[] {
const { width, height, data } = imageData;
// 1. 紫ピクセルのマスクを作成
const mask = new Uint8Array(width * height);
for (let i = 0; i < data.length; i += 4) {
const pixIdx = i / 4;
if (isPurplePixel(data[i], data[i + 1], data[i + 2])) {
mask[pixIdx] = 1;
}
}
// 2. グリッドクラスタリング (セルサイズはminRadiusに基づく)
const cellSize = Math.max(4, Math.floor(minRadius / 2));
const rawClusters = clusterPixels(mask, width, height, cellSize);
// 3. 近接クラスタを統合
const mergeRadius = maxRadius * 1.5;
const merged = mergeClusters(rawClusters, mergeRadius);
// 4. 各クラスタの形状を分析してコントロール円をフィルタ
const controls: DetectedControl[] = [];
for (const cluster of merged) {
// 最小ピクセル数のフィルタ (円弧の一部分を検出)
// コントロール円の円周上のピクセルを検出するので、
// 最小で円周の1/4程度 (紫ピクセルは円弧上にのみ存在)
const minPixels = minRadius * 1.5;
if (cluster.count < minPixels) continue;
// クラparseGpx function · typescript · L6-L35 (30 LOC)src/lib/tracking/gpx-parser.ts
export function parseGpx(gpxText: string): TrackPoint[] {
const parser = new DOMParser();
const doc = parser.parseFromString(gpxText, "application/xml");
const points: TrackPoint[] = [];
const trkpts = doc.querySelectorAll("trkpt");
if (trkpts.length === 0) return points;
let baseTime: number | null = null;
trkpts.forEach((pt) => {
const lat = parseFloat(pt.getAttribute("lat") ?? "0");
const lng = parseFloat(pt.getAttribute("lon") ?? "0");
const eleEl = pt.querySelector("ele");
const timeEl = pt.querySelector("time");
const elevation = eleEl ? parseFloat(eleEl.textContent ?? "0") : undefined;
let time = 0;
if (timeEl?.textContent) {
const ts = new Date(timeEl.textContent).getTime() / 1000;
if (baseTime === null) baseTime = ts;
time = Math.round(ts - baseTime);
}
points.push({ lat, lng, time, elevation });
});
return points;
}createRng function · typescript · L4-L9 (6 LOC)src/lib/tracking/sample-data.ts
function createRng(seed: number) {
return () => {
seed = (seed * 1664525 + 1013904223) & 0xffffffff;
return (seed >>> 0) / 0xffffffff;
};
}Powered by Repobility — scan your code at https://repobility.com
haversine function · typescript · L12-L20 (9 LOC)src/lib/tracking/sample-data.ts
function haversine(a: [number, number], b: [number, number]): number {
const R = 6371000;
const dLat = ((b[0] - a[0]) * Math.PI) / 180;
const dLng = ((b[1] - a[1]) * Math.PI) / 180;
const sa = Math.sin(dLat / 2);
const sb = Math.sin(dLng / 2);
const h = sa * sa + Math.cos((a[0] * Math.PI) / 180) * Math.cos((b[0] * Math.PI) / 180) * sb * sb;
return 2 * R * Math.asin(Math.sqrt(h));
}generateTrack function · typescript · L22-L26 (5 LOC)src/lib/tracking/sample-data.ts
function generateTrack(
courseControls: [number, number][],
avgSpeed: number,
rng: () => number
): { track: TrackPoint[]; controlTimes: number[] } {buildSplits function · typescript · L131-L160 (30 LOC)src/lib/tracking/sample-data.ts
function buildSplits(
allControlTimes: Record<string, number[]>,
courseOrder: string[],
participantIds: string[]
): SplitEntry[] {
const entries: SplitEntry[] = [];
for (let ci = 0; ci < courseOrder.length; ci++) {
const controlId = courseOrder[ci];
// Collect times for this control
const times = participantIds.map((pid) => ({
pid,
time: allControlTimes[pid]?.[ci] ?? 0,
leg: ci === 0 ? 0 : (allControlTimes[pid]?.[ci] ?? 0) - (allControlTimes[pid]?.[ci - 1] ?? 0),
}));
// Sort by cumulative time for placement
times.sort((a, b) => a.time - b.time);
times.forEach((t, i) => {
entries.push({
participantId: t.pid,
controlId,
time: t.time,
leg: t.leg,
place: i + 1,
});
});
}
return entries;
}cn function · typescript · L4-L6 (3 LOC)src/lib/utils.ts
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}formatDate function · typescript · L8-L15 (8 LOC)src/lib/utils.ts
export function formatDate(dateString: string): string {
const date = new Date(dateString);
return date.toLocaleDateString("ja-JP", {
year: "numeric",
month: "long",
day: "numeric",
});
}formatTime function · typescript · L17-L25 (9 LOC)src/lib/utils.ts
export function formatTime(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) {
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
}
return `${m}:${String(s).padStart(2, "0")}`;
}‹ prevpage 3 / 3