← back to kodanatlas__trails-jp

Function bodies 121 total

All specs Real LLM only Function bodies
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 && tokens2
matchLapCenterEvents 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