@jjlmoya/utils-nature 1.25.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/index.ts +2 -1
- package/src/entries.ts +4 -1
- package/src/index.ts +4 -0
- package/src/pages/[locale]/[slug].astro +5 -4
- package/src/tests/consumer_loader_contract.test.ts +24 -0
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/seedStratificationCalendar/bibliography.astro +9 -0
- package/src/tool/seedStratificationCalendar/bibliography.ts +16 -0
- package/src/tool/seedStratificationCalendar/calendar-actions.ts +66 -0
- package/src/tool/seedStratificationCalendar/component.astro +62 -0
- package/src/tool/seedStratificationCalendar/controller.ts +227 -0
- package/src/tool/seedStratificationCalendar/dom-views.ts +130 -0
- package/src/tool/seedStratificationCalendar/entry.ts +29 -0
- package/src/tool/seedStratificationCalendar/evaluator.ts +32 -0
- package/src/tool/seedStratificationCalendar/i18n/de.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/en.ts +149 -0
- package/src/tool/seedStratificationCalendar/i18n/es.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/fr.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/id.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/it.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/ja.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/ko.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/nl.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/pl.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/pt.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/ru.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/sv.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/tr.ts +35 -0
- package/src/tool/seedStratificationCalendar/i18n/zh.ts +35 -0
- package/src/tool/seedStratificationCalendar/index.ts +11 -0
- package/src/tool/seedStratificationCalendar/localized.ts +70 -0
- package/src/tool/seedStratificationCalendar/logic.test.ts +53 -0
- package/src/tool/seedStratificationCalendar/logic.ts +198 -0
- package/src/tool/seedStratificationCalendar/profile-labels.ts +18 -0
- package/src/tool/seedStratificationCalendar/profile-select.ts +43 -0
- package/src/tool/seedStratificationCalendar/seed-stratification-calendar.css +558 -0
- package/src/tool/seedStratificationCalendar/seo.astro +10 -0
- package/src/tool/seedStratificationCalendar/storage.ts +27 -0
- package/src/tool/seedStratificationCalendar/ui.ts +66 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/bibliography.astro +9 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/bibliography.ts +16 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/component.astro +115 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/controller.ts +81 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/dom-views.ts +98 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/entry.ts +29 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/evaluator.test.ts +22 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/evaluator.ts +11 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/de.ts +49 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/en.ts +189 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/es.ts +46 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/fr.ts +44 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/id.ts +44 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/it.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/ja.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/ko.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/nl.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/pl.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/pt.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/ru.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/sv.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/tr.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/i18n/zh.ts +43 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/index.ts +11 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/logic.test.ts +48 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/logic.ts +99 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/seo.astro +14 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/storage.ts +18 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/ui.ts +48 -0
- package/src/tool/wildlifeCameraTrapEffortPlanner/wildlife-camera-trap-effort-planner.css +442 -0
- package/src/tools.ts +5 -1
- package/src/types.ts +6 -5
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import { bibliography } from '../bibliography';
|
|
3
|
+
import type { WildlifeCameraTrapEffortPlannerLocaleContent } from '../entry';
|
|
4
|
+
|
|
5
|
+
const slug = 'planer-for-viltkamerors-insats';
|
|
6
|
+
const title = 'Planerare för insats med viltkameror';
|
|
7
|
+
const description = 'Planera viltkamerornas insats, uppskatta kameradagar och granskningsarbete och skissa en placering utan att uppskatta en population.';
|
|
8
|
+
const faqData = [
|
|
9
|
+
{ question: 'Vad är kameradagar?', answer: 'Kameradagar är antalet kameror multiplicerat med antalet aktiva dagar. De beskriver provtagningsinsatsen, inte antalet djur i området.' },
|
|
10
|
+
{ question: 'Beräknar planeraren populationsstorlek?', answer: 'Nej. Den uppskattar arbetsinsats och en nominell planeringsyta. Täthet, antal och förekomst kräver en validerad studiedesign som tar hänsyn till ofullständig upptäckt.' },
|
|
11
|
+
{ question: 'Vad gör avståndet mellan stationerna?', answer: 'Det skapar en nominell kvadratisk planeringscell per kamera och jämför målet med ett jämnt rutnätsavstånd. Det är inte en upptäcktsradie.' },
|
|
12
|
+
{ question: 'Hur använder jag granskningsschemat?', answer: 'Använd veckopunkterna för att planera besök, batterier, lagring och bildgranskning. Ersätt dem med de faktiska besöken i fältprotokollet.' },
|
|
13
|
+
];
|
|
14
|
+
const howToData = [
|
|
15
|
+
{ name: 'Beskriv undersökningsområdet', text: 'Ange området som kamerorna ska täcka. Metrisk eller imperial växling behåller det fysiska värdet.' },
|
|
16
|
+
{ name: 'Planera placeringen', text: 'Ange kameror, varaktighet och målavstånd. Skissen visar ett relativt planeringsrutnät, inte koordinater eller garanterade upptäcktszoner.' },
|
|
17
|
+
{ name: 'Uppskatta bildmängden', text: 'Ange förväntade händelser per kamera och dag. Planeraren multiplicerar värdet med kameradagar och fördelar det på kontroller.' },
|
|
18
|
+
{ name: 'Förbered fältanteckningar', text: 'Ge kameror och sessioner unika identiteter, skriv ned placeringen och registrera varje kontroll även utan upptäckt.' },
|
|
19
|
+
];
|
|
20
|
+
const faqSchema: WithContext<FAQPage> = { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqData.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
21
|
+
const howToSchema: WithContext<HowTo> = { '@context': 'https://schema.org', '@type': 'HowTo', name: title, description, step: howToData.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) };
|
|
22
|
+
const appSchema: WithContext<SoftwareApplication> = { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'UtilityApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'sv' };
|
|
23
|
+
|
|
24
|
+
export const content: WildlifeCameraTrapEffortPlannerLocaleContent = {
|
|
25
|
+
slug, title, description,
|
|
26
|
+
ui: {
|
|
27
|
+
areaPerCameraLabel: 'Yta per kamera',
|
|
28
|
+
labelSurveyArea: 'Undersökningsområde', labelCameraCount: 'Kamerastationer', labelDeploymentDays: 'Utplaceringsdagar', labelSpacing: 'Målavstånd mellan stationer', labelTriggerRate: 'Förväntade händelser per kamera och dag', unitHectares: 'ha', unitAcres: 'ac', unitMeters: 'm', unitFeet: 'ft', unitCameras: 'kameror', unitDays: 'dagar', unitEventsPerDay: 'händelser per dag', helpSurveyArea: 'Området som utplaceringen ska undersöka.', helpCameraCount: 'Aktiva kameror i kampanjen.', helpDeploymentDays: 'Dagar från montering till hämtning.', helpSpacing: 'Ett planeringsmått, inte en upptäcktsradie.', helpTriggerRate: 'Använd ett försiktigt värde från en liknande kampanj eller ett test.', unitSystemLabel: 'Mätsystem', metricLabel: 'Metriskt', imperialLabel: 'Imperialt', presetsTitle: 'Startpunkter för fältet', presetBackyard: 'Skogsbryn', presetWoodland: 'Blandskog', presetReserve: 'Stort reservat', mapLabel: 'Relativ skiss av placeringen', mapCaption: 'Numrerade stationer i ett relativt planeringsrutnät. Cellerna är inte en karta över livsmiljön.', stationLabel: 'station', cameraDaysLabel: 'Kameradagar', expectedEventsLabel: 'Förväntade händelser', spacingLabel: 'Avstånd i jämnt rutnät', reviewLoadLabel: 'Händelser per kontroll', reviewScheduleTitle: 'Kontrollrytm', reviewScheduleText: 'Veckopunkterna synliggör bildmängden före utplacering. Ersätt dem med protokollets besök.', reviewDayLabel: 'Dag', reviewEventsLabel: 'förväntade händelser', statusBalanced: 'Avståndet verkar balanserat', statusSparse: 'Stationerna verkar glest placerade', statusDense: 'Stationerna verkar tätt placerade', statusReviewHeavy: 'Granskningsbördan kräver uppmärksamhet', statusNeedsInput: 'Fyll i en fullständig plan', statusNote: 'Detta är en insatsplan. Den säger inte hur många djur som finns och garanterar inte upptäckt av en art.', assumptionTitle: 'Modellens antaganden', assumptionText: 'Den nominella ytan behandlar varje station som en kvadrat baserad på målavståndet. Förväntade händelser antar en konstant takt för alla kameror och dagar.', limitationTitle: 'Begränsningar i fält', limitationText: 'Välj platser efter art och fråga, inte bara geometri. Anteckna identiteter, datum, riktning, höjd, livsmiljö och kontroller utan upptäckt.',
|
|
29
|
+
},
|
|
30
|
+
seo: [
|
|
31
|
+
{ type: 'title', text: 'Planera insatsen med viltkameror', level: 2 },
|
|
32
|
+
{ type: 'paragraph', html: 'Ett projekt med viltkameror måste besvara två frågor före utplaceringen. Hur stor provtagningsinsats representerar planen? Kan det förväntade bildflödet lagras, granskas och dokumenteras? Planeraren gör detta till kameradagar, händelser, stationsskiss och kontrollpunkter.' },
|
|
33
|
+
{ type: 'title', text: 'Använd kameradagar för att beskriva insatsen', level: 3 },
|
|
34
|
+
{ type: 'paragraph', html: 'Kameradagar är <code>kamerastationer × utplaceringsdagar</code>. Tolv kameror under 28 dagar ger 336 kameradagar. Det hjälper att jämföra kampanjer men är ingen populationsuppskattning och korrigerar inte för fel, livsmiljö eller beteende.' },
|
|
35
|
+
{ type: 'list', items: ['<strong>Kamerastationer:</strong> enheter som är aktiva under kampanjen.', '<strong>Utplaceringsdagar:</strong> planerad tid från montering till hämtning.', '<strong>Händelsetakt:</strong> händelser per kamera och dag, inte varje bild i en serie.', '<strong>Kontroller:</strong> planera lagring, batterier, besök och granskning.'] },
|
|
36
|
+
{ type: 'title', text: 'Läs skissen som ett fälthjälpmedel', level: 3 },
|
|
37
|
+
{ type: 'paragraph', html: 'Skissen använder yta, antal kameror och målavstånd för en relativ placering. Den nominella ytan är antal stationer gånger avståndet i kvadrat. Det är inte sensorns yta: terräng, växtlighet, höjd, riktning och beteende ändrar upptäcktssannolikheten.' },
|
|
38
|
+
{ type: 'title', text: 'Förbered bättre anteckningar före utplacering', level: 3 },
|
|
39
|
+
{ type: 'paragraph', html: 'Bestäm målart och fråga. Ge varje kamera och session en stabil identitet. Registrera datum, plats, höjd, riktning, livsmiljö, inställningar och funktion vid montering och kontroll. Även en kontroll utan upptäckt är en observation.' },
|
|
40
|
+
{ type: 'tip', title: 'Gör inte insats till populationsantal', html: 'Viltkameror har ofullständig upptäckt. Använd planeraren för fält- och bildarbete och en validerad ekologisk design för förekomst, antal, täthet eller populationsförändring.' },
|
|
41
|
+
],
|
|
42
|
+
faq: faqData, howTo: howToData, bibliography, schemas: [faqSchema, howToSchema, appSchema],
|
|
43
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import { bibliography } from '../bibliography';
|
|
3
|
+
import type { WildlifeCameraTrapEffortPlannerLocaleContent } from '../entry';
|
|
4
|
+
|
|
5
|
+
const slug = 'yaban-hayati-fotokapan-caba-planlayici';
|
|
6
|
+
const title = 'Yaban hayatı fotokapan çabası planlayıcısı';
|
|
7
|
+
const description = 'Fotokapan çalışmasının çabasını planlayın, kamera günlerini ve görüntü inceleme yükünü tahmin edin; nüfus tahmini yapmayın.';
|
|
8
|
+
const faqData = [
|
|
9
|
+
{ question: 'Kamera günleri nedir?', answer: 'Kamera günleri, çalışan kamera sayısının etkin gün sayısıyla çarpımıdır. Örnekleme çabasını gösterir, alandaki hayvan sayısını göstermez.' },
|
|
10
|
+
{ question: 'Planlayıcı yaban hayatı nüfusunu hesaplar mı?', answer: 'Hayır. İşletme çabasını ve nominal bir planlama alanını tahmin eder. Sayı, yoğunluk ve kullanım için eksik tespiti dikkate alan doğrulanmış tasarım ve model gerekir.' },
|
|
11
|
+
{ question: 'İstasyon aralığı ne işe yarar?', answer: 'Her kamera için nominal kare hücre oluşturur ve hedefi alan ile kamera sayısından çıkan eşit ızgara aralığıyla karşılaştırır. Bu bir tespit yarıçapı değildir.' },
|
|
12
|
+
{ question: 'İnceleme programı nasıl kullanılmalı?', answer: 'Haftalık noktaları ziyaret, pil, depolama ve görüntü incelemesini planlamak için kullanın. Bunları gerçek saha protokolünüzdeki ziyaretlerle değiştirin.' },
|
|
13
|
+
];
|
|
14
|
+
const howToData = [
|
|
15
|
+
{ name: 'Araştırma alanını tanımlayın', text: 'Kamera yerleşiminin örnekleyeceği alanı girin. Metrik veya emperyal seçim fiziksel değeri korur.' },
|
|
16
|
+
{ name: 'Çabayı yerleşime dönüştürün', text: 'Kamera sayısını, süreyi ve hedef aralığı girin. Çizim göreli bir plan ızgarasıdır; koordinat veya garanti edilen tespit bölgesi değildir.' },
|
|
17
|
+
{ name: 'Görüntü yükünü tahmin edin', text: 'Kamera başına günlük beklenen olayları girin. Planlayıcı bunları kamera günleriyle çarpar ve kontrollere dağıtır.' },
|
|
18
|
+
{ name: 'Saha kayıtlarını hazırlayın', text: 'Kamera ve oturumlara benzersiz kimlik verin, yerleşimi yazın ve hayvan görülmese de her kontrolü kaydedin.' },
|
|
19
|
+
];
|
|
20
|
+
const faqSchema: WithContext<FAQPage> = { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqData.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
21
|
+
const howToSchema: WithContext<HowTo> = { '@context': 'https://schema.org', '@type': 'HowTo', name: title, description, step: howToData.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) };
|
|
22
|
+
const appSchema: WithContext<SoftwareApplication> = { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'UtilityApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'tr' };
|
|
23
|
+
|
|
24
|
+
export const content: WildlifeCameraTrapEffortPlannerLocaleContent = {
|
|
25
|
+
slug, title, description,
|
|
26
|
+
ui: {
|
|
27
|
+
areaPerCameraLabel: 'Kamera başına alan',
|
|
28
|
+
labelSurveyArea: 'Araştırma alanı', labelCameraCount: 'Kamera istasyonları', labelDeploymentDays: 'Yerleştirme günleri', labelSpacing: 'Hedef istasyon aralığı', labelTriggerRate: 'Kamera başına günlük beklenen olay', unitHectares: 'ha', unitAcres: 'akre', unitMeters: 'm', unitFeet: 'ft', unitCameras: 'kamera', unitDays: 'gün', unitEventsPerDay: 'günlük olay', helpSurveyArea: 'Yerleşimin örnekleyeceği alan.', helpCameraCount: 'Bu çalışmadaki etkin kameralar.', helpDeploymentDays: 'Kurulumdan toplamaya kadar günler.', helpSpacing: 'Planlama hücresi; tespit yarıçapı değildir.', helpTriggerRate: 'Benzer bir çalışma veya denemeden temkinli oran kullanın.', unitSystemLabel: 'Ölçüm sistemi', metricLabel: 'Metrik', imperialLabel: 'Emperyal', presetsTitle: 'Saha başlangıçları', presetBackyard: 'Orman kenarı', presetWoodland: 'Karışık orman', presetReserve: 'Büyük koruma alanı', mapLabel: 'Göreli yerleşim çizimi', mapCaption: 'Numaralı istasyonlar göreli plan ızgarında gösterilir. Hücreler habitat kapsama haritası değildir.', stationLabel: 'istasyon', cameraDaysLabel: 'Kamera günleri', expectedEventsLabel: 'Beklenen olaylar', spacingLabel: 'Eşit ızgara aralığı', reviewLoadLabel: 'İnceleme başına olay', reviewScheduleTitle: 'İnceleme ritmi', reviewScheduleText: 'Haftalık noktalar yerleşimden önce görüntü yükünü görünür kılar. Kendi protokolünüzdeki ziyaretlerle değiştirin.', reviewDayLabel: 'Gün', reviewEventsLabel: 'beklenen olay', statusBalanced: 'Aralık dengeli görünüyor', statusSparse: 'İstasyonlar uzak görünüyor', statusDense: 'İstasyonlar yakın görünüyor', statusReviewHeavy: 'İnceleme yüküne dikkat edin', statusNeedsInput: 'Yerleşim planını tamamlayın', statusNote: 'Bu bir çaba planıdır. Alandaki hayvan sayısını söylemez ve tür tespitini garanti etmez.', assumptionTitle: 'Model varsayımları', assumptionText: 'Nominal alan her istasyonu hedef aralığa dayalı kare hücre sayar. Beklenen olaylar tüm kamera ve günlerde sabit oran varsayar.', limitationTitle: 'Saha sınırları', limitationText: 'Konumları yalnız geometriye göre değil, tür ve soruya göre seçin. Kimlikleri, tarihleri, yönü, yüksekliği, habitatı ve boş kontrolleri kaydedin.',
|
|
29
|
+
},
|
|
30
|
+
seo: [
|
|
31
|
+
{ type: 'title', text: 'Fotokapan çalışma çabasını planlayın', level: 2 },
|
|
32
|
+
{ type: 'paragraph', html: 'Bir fotokapan projesi yerleştirmeden önce iki soruya cevap vermelidir. Yerleşim ne kadar örnekleme çabası yaratıyor? Beklenen görüntü akışı saklanabilir, incelenebilir ve belgelenebilir mi? Bu planlayıcı bunları kamera günleri, olaylar, istasyon çizimi ve kontroller olarak düzenler.' },
|
|
33
|
+
{ type: 'title', text: 'Çabayı kamera günleriyle anlatın', level: 3 },
|
|
34
|
+
{ type: 'paragraph', html: 'Kamera günleri <code>kamera istasyonları × yerleştirme günleri</code> şeklinde hesaplanır. On iki kamera 28 gün çalışırsa 336 kamera günü oluşur. Bu kampanyaları karşılaştırır, ancak nüfus tahmini değildir ve arıza, habitat ya da davranışı düzeltmez.' },
|
|
35
|
+
{ type: 'list', items: ['<strong>İstasyonlar:</strong> kampanyada çalışan cihazlar.', '<strong>Günler:</strong> kurulumdan toplamaya kadar planlanan süre.', '<strong>Olay oranı:</strong> seri çekimdeki her fotoğraf değil, kamera başına günlük olay.', '<strong>Kontroller:</strong> depolama, pil, ziyaret ve incelemeyi planlayın.'] },
|
|
36
|
+
{ type: 'title', text: 'Çizimi saha yardımcısı olarak okuyun', level: 3 },
|
|
37
|
+
{ type: 'paragraph', html: 'Çizim alanı, kamera sayısını ve hedef aralığı göreli bir yerleşim için kullanır. Nominal alan istasyon sayısının aralığın karesiyle çarpımıdır. Sensör alanı değildir; arazi, bitki örtüsü, yükseklik, yön ve davranış tespit olasılığını değiştirir.' },
|
|
38
|
+
{ type: 'title', text: 'Kurulumdan önce kayıtları hazırlayın', level: 3 },
|
|
39
|
+
{ type: 'paragraph', html: 'Hedef türü ve araştırma sorusunu belirleyin. Her kamera ve oturuma kalıcı kimlik verin. Tarih, konum, yükseklik, yön, habitat, ayarlar ve çalışma durumunu kaydedin. Tespit olmayan kontrol de gerçek bir saha gözlemidir.' },
|
|
40
|
+
{ type: 'tip', title: 'Çabayı hayvan sayısına çevirmeyin', html: 'Fotokapanlarda eksik tespit vardır. Saha ve görüntü bütçesi için planlayıcıyı, kullanım, sayı, yoğunluk veya nüfus değişimi için doğrulanmış ekolojik tasarımı kullanın.' },
|
|
41
|
+
],
|
|
42
|
+
faq: faqData, howTo: howToData, bibliography, schemas: [faqSchema, howToSchema, appSchema],
|
|
43
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import { bibliography } from '../bibliography';
|
|
3
|
+
import type { WildlifeCameraTrapEffortPlannerLocaleContent } from '../entry';
|
|
4
|
+
|
|
5
|
+
const slug = 'wildlife-camera-trap-effort-planner';
|
|
6
|
+
const title = '野生动物红外相机调查工作量规划器';
|
|
7
|
+
const description = '规划红外相机调查工作量,估算相机日和图像审核负担,并绘制不用于种群估计的布设方案。';
|
|
8
|
+
const faqData = [
|
|
9
|
+
{ question: '什么是相机日?', answer: '相机日是运行中的相机数量乘以运行天数。它表示抽样工作量,不表示区域内的动物数量。' },
|
|
10
|
+
{ question: '规划器会计算野生动物种群数量吗?', answer: '不会。它估算运行工作量和名义规划面积。数量、密度和占域率需要经过验证的调查设计与能够处理不完全探测的统计模型。' },
|
|
11
|
+
{ question: '相机间距有什么作用?', answer: '它为每台相机建立名义方形规划单元,并将目标间距与根据面积和相机数量得到的均匀网格间距进行比较。它不是探测半径。' },
|
|
12
|
+
{ question: '如何使用审核计划?', answer: '用每周检查点规划访问、电池、存储和图像审核。根据实际野外协议,将检查点替换为真实访问记录。' },
|
|
13
|
+
];
|
|
14
|
+
const howToData = [
|
|
15
|
+
{ name: '填写调查面积', text: '输入相机布设计划覆盖的面积。切换公制或英制时,物理数值保持一致。' },
|
|
16
|
+
{ name: '规划相机布设', text: '输入相机数量、布设时长和目标间距。示意图是相对规划网格,不是坐标或保证的探测范围。' },
|
|
17
|
+
{ name: '估算图像工作量', text: '输入每台相机每天的预期事件数。规划器将其乘以相机日,并分配到每周检查点。' },
|
|
18
|
+
{ name: '准备野外记录', text: '为相机和布设会话分配唯一编号,记录安装信息,并记录每次检查,包括没有发现动物的检查。' },
|
|
19
|
+
];
|
|
20
|
+
const faqSchema: WithContext<FAQPage> = { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqData.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
21
|
+
const howToSchema: WithContext<HowTo> = { '@context': 'https://schema.org', '@type': 'HowTo', name: title, description, step: howToData.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) };
|
|
22
|
+
const appSchema: WithContext<SoftwareApplication> = { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'UtilityApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'zh' };
|
|
23
|
+
|
|
24
|
+
export const content: WildlifeCameraTrapEffortPlannerLocaleContent = {
|
|
25
|
+
slug, title, description,
|
|
26
|
+
ui: {
|
|
27
|
+
areaPerCameraLabel: '每台相机的面积',
|
|
28
|
+
labelSurveyArea: '调查面积', labelCameraCount: '相机点位', labelDeploymentDays: '布设天数', labelSpacing: '目标点位间距', labelTriggerRate: '每台相机每天的预期事件', unitHectares: '公顷', unitAcres: '英亩', unitMeters: '米', unitFeet: '英尺', unitCameras: '台', unitDays: '天', unitEventsPerDay: '事件/天', helpSurveyArea: '计划进行相机调查的面积。', helpCameraCount: '本次调查中运行的相机数量。', helpDeploymentDays: '从安装到回收的天数。', helpSpacing: '规划单元大小,不是探测半径。', helpTriggerRate: '参考相似调查或试布设,使用谨慎的数值。', unitSystemLabel: '测量单位', metricLabel: '公制', imperialLabel: '英制', presetsTitle: '野外起始方案', presetBackyard: '林缘', presetWoodland: '混合林', presetReserve: '大型保护区', mapLabel: '相对布设示意图', mapCaption: '编号相机排列在相对规划网格中。单元用于辅助间距规划,不是生境覆盖地图。', stationLabel: '点位', cameraDaysLabel: '相机日', expectedEventsLabel: '预期事件', spacingLabel: '均匀网格间距', reviewLoadLabel: '每次检查的事件', reviewScheduleTitle: '检查节奏', reviewScheduleText: '每周检查点能在布设前展示图像工作量。请根据实际野外协议调整。', reviewDayLabel: '第', reviewEventsLabel: '预期事件', statusBalanced: '间距看起来较均衡', statusSparse: '点位间距看起来较大', statusDense: '点位间距看起来较小', statusReviewHeavy: '图像审核负担需要关注', statusNeedsInput: '请填写完整布设计划', statusNote: '这是工作量计划,不会告诉你区域内有多少动物,也不保证发现某个物种。', assumptionTitle: '模型假设', assumptionText: '名义面积把每个点位视为由目标间距形成的正方形单元。预期事件假设所有相机和日期的触发率保持不变。', limitationTitle: '野外限制', limitationText: '根据目标物种和研究问题选择点位,而不只是追求几何整齐。记录编号、日期、方向、高度、生境以及空检结果。',
|
|
29
|
+
},
|
|
30
|
+
seo: [
|
|
31
|
+
{ type: 'title', text: '规划红外相机调查工作量', level: 2 },
|
|
32
|
+
{ type: 'paragraph', html: '野生动物红外相机项目在布设前需要回答两个问题:这次布设代表多少抽样工作量?预期图像流能否被存储、审核和记录?本规划器将这些问题整理为相机日、预期事件、点位示意图和检查节点。' },
|
|
33
|
+
{ type: 'title', text: '用相机日描述调查工作量', level: 3 },
|
|
34
|
+
{ type: 'paragraph', html: '相机日等于 <code>相机点位 × 布设天数</code>。12台相机运行28天会产生336相机日。这个数字可以比较不同调查,但不是种群估计,也不会修正设备故障、生境差异或动物行为。' },
|
|
35
|
+
{ type: 'list', items: ['<strong>相机点位:</strong>调查期间运行的设备数量。', '<strong>布设天数:</strong>从安装到回收的计划时间。', '<strong>触发率:</strong>使用每台相机每天的事件数,而不是连拍中的每张照片。', '<strong>检查节点:</strong>据此规划存储、电池、访问和图像审核。'] },
|
|
36
|
+
{ type: 'title', text: '把示意图当作野外规划辅助', level: 3 },
|
|
37
|
+
{ type: 'paragraph', html: '示意图使用面积、相机数量和目标间距展示相对布设。名义面积等于点位数量乘以目标间距的平方。它不是传感器探测面积,地形、植被、道路、方向、高度和动物行为都会改变探测概率。' },
|
|
38
|
+
{ type: 'title', text: '布设前准备更完整的记录', level: 3 },
|
|
39
|
+
{ type: 'paragraph', html: '先确定目标物种和研究问题,为每台相机及每次布设设置稳定编号。在安装和检查时记录日期、位置、高度、方向、生境、参数和设备状态。没有拍到动物的检查也属于调查过程记录。' },
|
|
40
|
+
{ type: 'tip', title: '不要把工作量变成动物数量', html: '红外相机存在不完全探测。用规划器预算野外和图像工作,用经过验证的生态学设计分析占域率、数量、密度或种群变化。' },
|
|
41
|
+
],
|
|
42
|
+
faq: faqData, howTo: howToData, bibliography, schemas: [faqSchema, howToSchema, appSchema],
|
|
43
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { wildlifeCameraTrapEffortPlanner } from './entry';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
|
|
6
|
+
export const WILDLIFE_CAMERA_TRAP_EFFORT_PLANNER_TOOL: ToolDefinition = {
|
|
7
|
+
entry: wildlifeCameraTrapEffortPlanner,
|
|
8
|
+
Component: () => import('./component.astro'),
|
|
9
|
+
SEOComponent: () => import('./seo.astro'),
|
|
10
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
11
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { calculateSurveyPlan, DEFAULT_INPUTS, M2_PER_HECTARE } from './logic';
|
|
3
|
+
|
|
4
|
+
describe('Wildlife camera trap effort planner logic', () => {
|
|
5
|
+
it('calculates effort, expected events, nominal footprint, and review checkpoints', () => {
|
|
6
|
+
const result = calculateSurveyPlan(DEFAULT_INPUTS);
|
|
7
|
+
|
|
8
|
+
expect(result.cameraDays).toBe(336);
|
|
9
|
+
expect(result.expectedEvents).toBe(1344);
|
|
10
|
+
expect(result.areaPerCameraM2).toBe(10000);
|
|
11
|
+
expect(result.nominalFootprintM2).toBe(120000);
|
|
12
|
+
expect(result.nominalFootprintPercent).toBeCloseTo(100);
|
|
13
|
+
expect(result.evenGridSpacingMeters).toBeCloseTo(Math.sqrt(M2_PER_HECTARE));
|
|
14
|
+
expect(result.reviewCheckpoints.map((checkpoint) => checkpoint.day)).toEqual([7, 14, 21, 28]);
|
|
15
|
+
expect(result.expectedEventsPerReview).toBe(336);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('keeps physical area calculations independent from input units', () => {
|
|
19
|
+
const result = calculateSurveyPlan({
|
|
20
|
+
areaHectares: 1,
|
|
21
|
+
cameraCount: 4,
|
|
22
|
+
deploymentDays: 10,
|
|
23
|
+
spacingMeters: 100,
|
|
24
|
+
triggerRatePerCameraDay: 2.5,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(result.areaM2).toBe(10000);
|
|
28
|
+
expect(result.cameraDays).toBe(40);
|
|
29
|
+
expect(result.expectedEvents).toBe(100);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('normalizes invalid and fractional planning values safely', () => {
|
|
33
|
+
const result = calculateSurveyPlan({
|
|
34
|
+
areaHectares: -1,
|
|
35
|
+
cameraCount: 2.9,
|
|
36
|
+
deploymentDays: Number.NaN,
|
|
37
|
+
spacingMeters: Number.POSITIVE_INFINITY,
|
|
38
|
+
triggerRatePerCameraDay: -3,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(result.areaHectares).toBe(0);
|
|
42
|
+
expect(result.cameraCount).toBe(2);
|
|
43
|
+
expect(result.deploymentDays).toBe(0);
|
|
44
|
+
expect(result.spacingMeters).toBe(0);
|
|
45
|
+
expect(result.expectedEvents).toBe(0);
|
|
46
|
+
expect(result.reviewCheckpoints).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export interface SurveyInput {
|
|
2
|
+
areaHectares: number;
|
|
3
|
+
cameraCount: number;
|
|
4
|
+
deploymentDays: number;
|
|
5
|
+
spacingMeters: number;
|
|
6
|
+
triggerRatePerCameraDay: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ReviewCheckpoint {
|
|
10
|
+
day: number;
|
|
11
|
+
expectedEvents: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SurveyPlanResult {
|
|
15
|
+
areaHectares: number;
|
|
16
|
+
areaM2: number;
|
|
17
|
+
cameraCount: number;
|
|
18
|
+
deploymentDays: number;
|
|
19
|
+
spacingMeters: number;
|
|
20
|
+
triggerRatePerCameraDay: number;
|
|
21
|
+
cameraDays: number;
|
|
22
|
+
expectedEvents: number;
|
|
23
|
+
areaPerCameraM2: number;
|
|
24
|
+
nominalFootprintM2: number;
|
|
25
|
+
nominalFootprintPercent: number;
|
|
26
|
+
evenGridSpacingMeters: number;
|
|
27
|
+
reviewCheckpoints: ReviewCheckpoint[];
|
|
28
|
+
expectedEventsPerReview: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_INPUTS: SurveyInput = {
|
|
32
|
+
areaHectares: 12,
|
|
33
|
+
cameraCount: 12,
|
|
34
|
+
deploymentDays: 28,
|
|
35
|
+
spacingMeters: 100,
|
|
36
|
+
triggerRatePerCameraDay: 4,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const METERS_PER_FOOT = 0.3048;
|
|
40
|
+
export const M2_PER_HECTARE = 10000;
|
|
41
|
+
export const M2_PER_ACRE = 4046.8564224;
|
|
42
|
+
export const ACRES_PER_HECTARE = M2_PER_HECTARE / M2_PER_ACRE;
|
|
43
|
+
|
|
44
|
+
function positive(value: number): number {
|
|
45
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function wholePositive(value: number): number {
|
|
49
|
+
return Math.floor(positive(value));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeInput(input: SurveyInput): SurveyInput {
|
|
53
|
+
return {
|
|
54
|
+
areaHectares: positive(input.areaHectares),
|
|
55
|
+
cameraCount: wholePositive(input.cameraCount),
|
|
56
|
+
deploymentDays: wholePositive(input.deploymentDays),
|
|
57
|
+
spacingMeters: positive(input.spacingMeters),
|
|
58
|
+
triggerRatePerCameraDay: positive(input.triggerRatePerCameraDay),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildReviewCheckpoints(deploymentDays: number, expectedEventsPerDay: number): ReviewCheckpoint[] {
|
|
63
|
+
if (deploymentDays === 0) return [];
|
|
64
|
+
const checkpoints: ReviewCheckpoint[] = [];
|
|
65
|
+
for (let day = 7; day < deploymentDays; day += 7) {
|
|
66
|
+
checkpoints.push({ day, expectedEvents: day * expectedEventsPerDay });
|
|
67
|
+
}
|
|
68
|
+
checkpoints.push({ day: deploymentDays, expectedEvents: deploymentDays * expectedEventsPerDay });
|
|
69
|
+
return checkpoints;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function calculateSurveyPlan(input: SurveyInput): SurveyPlanResult {
|
|
73
|
+
const { areaHectares, cameraCount, deploymentDays, spacingMeters, triggerRatePerCameraDay } = normalizeInput(input);
|
|
74
|
+
const areaM2 = areaHectares * M2_PER_HECTARE;
|
|
75
|
+
const cameraDays = cameraCount * deploymentDays;
|
|
76
|
+
const expectedEvents = cameraDays * triggerRatePerCameraDay;
|
|
77
|
+
const areaPerCameraM2 = cameraCount > 0 ? areaM2 / cameraCount : 0;
|
|
78
|
+
const nominalFootprintM2 = cameraCount * spacingMeters ** 2;
|
|
79
|
+
const nominalFootprintPercent = areaM2 > 0 ? (nominalFootprintM2 / areaM2) * 100 : 0;
|
|
80
|
+
const evenGridSpacingMeters = cameraCount > 0 ? Math.sqrt(areaM2 / cameraCount) : 0;
|
|
81
|
+
const reviewCheckpoints = buildReviewCheckpoints(deploymentDays, cameraCount * triggerRatePerCameraDay);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
areaHectares,
|
|
85
|
+
areaM2,
|
|
86
|
+
cameraCount,
|
|
87
|
+
deploymentDays,
|
|
88
|
+
spacingMeters,
|
|
89
|
+
triggerRatePerCameraDay,
|
|
90
|
+
cameraDays,
|
|
91
|
+
expectedEvents,
|
|
92
|
+
areaPerCameraM2,
|
|
93
|
+
nominalFootprintM2,
|
|
94
|
+
nominalFootprintPercent,
|
|
95
|
+
evenGridSpacingMeters,
|
|
96
|
+
reviewCheckpoints,
|
|
97
|
+
expectedEventsPerReview: reviewCheckpoints.length > 0 ? expectedEvents / reviewCheckpoints.length : 0,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { wildlifeCameraTrapEffortPlanner } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props;
|
|
11
|
+
const content = await wildlifeCameraTrapEffortPlanner.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && content.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type UnitSystem = 'metric' | 'imperial';
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = 'jjlmoya:nature:wildlife-camera-trap-effort-planner:unit-system';
|
|
4
|
+
|
|
5
|
+
export function loadUnitSystem(): UnitSystem {
|
|
6
|
+
try {
|
|
7
|
+
const stored = window.localStorage.getItem(STORAGE_KEY);
|
|
8
|
+
return stored === 'imperial' ? 'imperial' : 'metric';
|
|
9
|
+
} catch {
|
|
10
|
+
return 'metric';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function saveUnitSystem(unitSystem: UnitSystem): void {
|
|
15
|
+
try {
|
|
16
|
+
window.localStorage.setItem(STORAGE_KEY, unitSystem);
|
|
17
|
+
} catch {}
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface WildlifeCameraTrapEffortPlannerUI {
|
|
2
|
+
labelSurveyArea: string;
|
|
3
|
+
labelCameraCount: string;
|
|
4
|
+
labelDeploymentDays: string;
|
|
5
|
+
labelSpacing: string;
|
|
6
|
+
labelTriggerRate: string;
|
|
7
|
+
unitHectares: string;
|
|
8
|
+
unitAcres: string;
|
|
9
|
+
unitMeters: string;
|
|
10
|
+
unitFeet: string;
|
|
11
|
+
unitCameras: string;
|
|
12
|
+
unitDays: string;
|
|
13
|
+
unitEventsPerDay: string;
|
|
14
|
+
helpSurveyArea: string;
|
|
15
|
+
helpCameraCount: string;
|
|
16
|
+
helpDeploymentDays: string;
|
|
17
|
+
helpSpacing: string;
|
|
18
|
+
helpTriggerRate: string;
|
|
19
|
+
unitSystemLabel: string;
|
|
20
|
+
metricLabel: string;
|
|
21
|
+
imperialLabel: string;
|
|
22
|
+
presetsTitle: string;
|
|
23
|
+
presetBackyard: string;
|
|
24
|
+
presetWoodland: string;
|
|
25
|
+
presetReserve: string;
|
|
26
|
+
mapLabel: string;
|
|
27
|
+
mapCaption: string;
|
|
28
|
+
stationLabel: string;
|
|
29
|
+
cameraDaysLabel: string;
|
|
30
|
+
expectedEventsLabel: string;
|
|
31
|
+
areaPerCameraLabel: string;
|
|
32
|
+
spacingLabel: string;
|
|
33
|
+
reviewLoadLabel: string;
|
|
34
|
+
reviewScheduleTitle: string;
|
|
35
|
+
reviewScheduleText: string;
|
|
36
|
+
reviewDayLabel: string;
|
|
37
|
+
reviewEventsLabel: string;
|
|
38
|
+
statusBalanced: string;
|
|
39
|
+
statusSparse: string;
|
|
40
|
+
statusDense: string;
|
|
41
|
+
statusReviewHeavy: string;
|
|
42
|
+
statusNeedsInput: string;
|
|
43
|
+
statusNote: string;
|
|
44
|
+
assumptionTitle: string;
|
|
45
|
+
assumptionText: string;
|
|
46
|
+
limitationTitle: string;
|
|
47
|
+
limitationText: string;
|
|
48
|
+
}
|